From 3c20fa02e977e8c3c7991ae34fb7a327628e968f Mon Sep 17 00:00:00 2001 From: Daniel Bengl Date: Sat, 14 Feb 2026 23:38:48 +0100 Subject: [PATCH 01/15] optimze data cache locality --- src/shared/chunk/chunk_data.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shared/chunk/chunk_data.rs b/src/shared/chunk/chunk_data.rs index 74fd7bf..ae2e473 100644 --- a/src/shared/chunk/chunk_data.rs +++ b/src/shared/chunk/chunk_data.rs @@ -75,7 +75,7 @@ impl Chunk { let n = PADDED_CHUNK_SIZE; assert!(x < n && y < n && z < n, "Index out of bounds: ({}, {}, {})", x,y,z); - x + n * (y + n * z) + z + n * (y + n * x) } pub fn key_eq_pos(key: [i32; 3], position: IVec3) -> bool { From d1fee36b0557e8fcb2e06322e2be6aa9844f1f00 Mon Sep 17 00:00:00 2001 From: Daniel Bengl Date: Sun, 15 Feb 2026 00:30:48 +0100 Subject: [PATCH 02/15] Wip update vec3 -> vec2 --- src/client/collider/systems.rs | 2 +- src/client/player/resources.rs | 4 +- src/client/terrain/components.rs | 2 +- src/client/terrain/events.rs | 8 +- src/client/terrain/resources.rs | 24 +++-- src/client/terrain/systems.rs | 28 +++--- src/server/terrain/config.rs | 4 +- src/server/terrain/persistence.rs | 6 +- src/server/terrain/resources.rs | 6 +- src/server/terrain/systems.rs | 4 +- src/server/terrain/util/generator.rs | 5 +- src/shared/chunk/chunk_data.rs | 7 +- src/shared/chunk/manager.rs | 132 +++++++++++++-------------- src/shared/chunk_serializer.rs | 4 +- src/shared/networking.rs | 4 +- 15 files changed, 113 insertions(+), 127 deletions(-) diff --git a/src/client/collider/systems.rs b/src/client/collider/systems.rs index 390347c..43f3ac9 100644 --- a/src/client/collider/systems.rs +++ b/src/client/collider/systems.rs @@ -127,7 +127,7 @@ mod tests { let block = BlockId::Dirt; let mut resource = app.world_mut().get_resource_mut::().unwrap(); - let chunks = ChunkManager::instantiate_chunks(IVec3::ZERO, IVec3::ONE); + let chunks = ChunkManager::instantiate_chunks(IVec2::ZERO, IVec2::ONE); resource.insert_chunks(chunks); resource.update_block(IVec3 { x: 6, y: 7, z: 8 }, block); diff --git a/src/client/player/resources.rs b/src/client/player/resources.rs index 09684c1..7f6ae92 100644 --- a/src/client/player/resources.rs +++ b/src/client/player/resources.rs @@ -48,7 +48,7 @@ impl LastPlayerPosition { Self(IVec3::ZERO) } - pub fn chunk_position(&self) -> IVec3 { + pub fn chunk_position(&self) -> IVec2 { Self::chunk_pos(self.0) } @@ -56,7 +56,7 @@ impl LastPlayerPosition { Self::chunk_pos(self.0) == Self::chunk_pos(other_world_position) } - fn chunk_pos(world_pos: IVec3) -> IVec3 { + fn chunk_pos(world_pos: IVec3) -> IVec2 { ChunkManager::world_position_to_chunk_position(world_pos) } } diff --git a/src/client/terrain/components.rs b/src/client/terrain/components.rs index 05c7824..87198c3 100644 --- a/src/client/terrain/components.rs +++ b/src/client/terrain/components.rs @@ -4,6 +4,6 @@ use super::resources::MeshType; #[derive(Component)] pub struct ChunkMesh { - pub key: [i32; 3], + pub key: [i32; 2], pub mesh_type: MeshType, } diff --git a/src/client/terrain/events.rs b/src/client/terrain/events.rs index 2e91298..7b57445 100644 --- a/src/client/terrain/events.rs +++ b/src/client/terrain/events.rs @@ -2,22 +2,22 @@ use crate::prelude::*; #[derive(Message)] pub struct ChunkMeshUpdateEvent { - pub chunk_position: IVec3, + pub chunk_position: IVec2, } #[derive(Message)] pub struct RerequestChunks { - pub center_chunk_position: IVec3, + pub center_chunk_position: IVec2, } #[derive(Message)] pub struct RequestChunkBatch { - pub positions: Vec, + pub positions: Vec, } #[derive(Message)] pub struct CleanupChunksAroundOrigin { - pub center_chunk_position: IVec3, + pub center_chunk_position: IVec2, } #[derive(Message)] diff --git a/src/client/terrain/resources.rs b/src/client/terrain/resources.rs index a5f5d77..03394a9 100644 --- a/src/client/terrain/resources.rs +++ b/src/client/terrain/resources.rs @@ -15,7 +15,7 @@ impl SpawnRegionLoaded { #[derive(Resource, Default)] pub struct RequestedChunks { - pub previous_chunks: HashSet, + pub previous_chunks: HashSet, } #[derive(Eq, Hash, Clone, PartialEq)] @@ -31,7 +31,7 @@ pub struct ChunkMeshes { pub struct MeshTask(pub Task); pub struct FutureChunkMesh { - pub position: IVec3, + pub position: IVec2, pub meshes_task: MeshTask, } @@ -42,12 +42,12 @@ pub struct MesherTasks { #[derive(Resource, Default)] pub struct ChunkEntityMap { - map: HashMap>, + map: HashMap>, } #[derive(Resource, Default)] pub struct SpawnRegion { - pub origin_chunk_position: IVec3, + pub origin_chunk_position: IVec2, } impl SpawnRegion { @@ -63,25 +63,23 @@ impl ChunkEntityMap { self.map.len() } - pub fn add(&mut self, chunk_position: IVec3, entity: Entity) { + pub fn add(&mut self, chunk_position: IVec2, entity: Entity) { self.map.entry(chunk_position).or_default().push(entity); } - pub fn remove(&mut self, chunk_position: IVec3) -> Option> { + pub fn remove(&mut self, chunk_position: IVec2) -> Option> { self.map.remove(&chunk_position) } pub fn extract_outside_distance( &mut self, - origin: &IVec3, - distance: &IVec3, - ) -> Vec<(IVec3, Vec)> { - let extracted: HashMap> = self + origin: &IVec2, + distance: &IVec2, + ) -> Vec<(IVec2, Vec)> { + let extracted: HashMap> = self .map .extract_if(|k, _v| { - (k.x - origin.x).abs() > distance.x - || (k.y - origin.y).abs() > distance.y - || (k.z - origin.z).abs() > distance.z + (k[0] - origin[0]).abs() > distance[0] || (k[1] - origin[1]).abs() > distance[1] }) .collect(); diff --git a/src/client/terrain/systems.rs b/src/client/terrain/systems.rs index cf9c86f..2e8d139 100644 --- a/src/client/terrain/systems.rs +++ b/src/client/terrain/systems.rs @@ -8,9 +8,9 @@ use terrain_resources::{ use crate::prelude::*; -const RENDER_DISTANCE: IVec3 = IVec3::new(4, 4, 4); -const CLEANUP_DISTANCE: IVec3 = IVec3::new(6, 6, 6); -const MIN_SPAWN_AREA_DISTANCE: IVec3 = IVec3::new(1, 1, 1); +const RENDER_DISTANCE: IVec2 = IVec2::new(4, 4); +const CLEANUP_DISTANCE: IVec2 = IVec2::new(6, 6); +const MIN_SPAWN_AREA_DISTANCE: IVec2 = IVec2::new(1, 1); pub fn prepare_mesher_materials_system( mut render_materials: ResMut, @@ -60,7 +60,7 @@ pub fn handle_chunk_request_chunk_batch_event_system( return; } - let mut new_positions: HashSet = HashSet::new(); + let mut new_positions: HashSet = HashSet::new(); for batch_event in batch_events.read() { batch_event.positions.iter().for_each(|position| { new_positions.insert(*position); @@ -68,8 +68,8 @@ pub fn handle_chunk_request_chunk_batch_event_system( } let old_positions = &all_requests.previous_chunks; - let diff: HashSet<&IVec3> = new_positions.difference(old_positions).collect(); - let diff: Vec = diff.into_iter().copied().collect(); + let diff: HashSet<&IVec2> = new_positions.difference(old_positions).collect(); + let diff: Vec = diff.into_iter().copied().collect(); let batched_positions = diff.chunks(32); @@ -166,7 +166,7 @@ pub fn handle_chunk_tasks_system( completed += 1; let pos = future_chunk.position; - let pos_vec = pos.as_vec3(); + let pos_vec = pos.as_vec2(); if let Some(entities) = chunk_entities.remove(pos) { entities.iter().for_each(|entity| { @@ -246,7 +246,7 @@ pub fn check_if_spawn_area_is_loaded_system( fn create_chunk_bundle( mesh_handle: Handle, - chunk_position: Vec3, + chunk_position: Vec2, mesh_type: MeshType, material_handle: Handle, ) -> ( @@ -258,16 +258,12 @@ fn create_chunk_bundle( ( Mesh3d(mesh_handle), Transform::from_xyz( - chunk_position.x * CHUNK_SIZE as f32, - chunk_position.y * CHUNK_SIZE as f32, - chunk_position.z * CHUNK_SIZE as f32, + chunk_position[0] * CHUNK_SIZE as f32, + 0.0, + chunk_position[1] * CHUNK_SIZE as f32, ), terrain_components::ChunkMesh { - key: [ - chunk_position.x as i32, - chunk_position.y as i32, - chunk_position.z as i32, - ], + key: [chunk_position[0] as i32, chunk_position[1] as i32], mesh_type, }, MeshMaterial3d(material_handle), diff --git a/src/server/terrain/config.rs b/src/server/terrain/config.rs index f8877ff..04fc7af 100644 --- a/src/server/terrain/config.rs +++ b/src/server/terrain/config.rs @@ -9,7 +9,7 @@ pub struct WorldConfig { pub world_extension: String, pub world_save_interval_seconds: i64, pub world_backup_interval_seconds: i64, - pub spawn_area_distance: IVec3, + pub spawn_area_distance: IVec2, } impl Default for WorldConfig { @@ -20,7 +20,7 @@ impl Default for WorldConfig { world_extension: String::from(".rsmcw"), world_save_interval_seconds: 30, world_backup_interval_seconds: 180, - spawn_area_distance: IVec3::new(4, 3, 4), + spawn_area_distance: IVec2::new(4, 4), } } } diff --git a/src/server/terrain/persistence.rs b/src/server/terrain/persistence.rs index a950316..80af382 100644 --- a/src/server/terrain/persistence.rs +++ b/src/server/terrain/persistence.rs @@ -147,7 +147,7 @@ mod tests { generator.params.density.squash_factor = 6.7; let mut chunk_manager = ChunkManager::new(); - let mut chunks = ChunkManager::instantiate_chunks(IVec3::ZERO, IVec3::ONE); + let mut chunks = ChunkManager::instantiate_chunks(IVec2::ZERO, IVec2::ONE); assert!(!chunks.is_empty()); @@ -168,7 +168,7 @@ mod tests { #[test] fn test_world_continuation() { let generator = Generator::with_seed(0); - let mut possible_new_chunk = Chunk::new(IVec3::new(20, 0, 20)); + let mut possible_new_chunk = Chunk::new(IVec2::new(20, 20)); generator.generate_chunk(&mut possible_new_chunk); save_world("my_world", &ChunkManager::new(), &generator).unwrap(); @@ -176,7 +176,7 @@ mod tests { let world = read_world_save_by_name("my_world").unwrap(); let generator = world.generator; - let mut actual_new_chunk = Chunk::new(IVec3::new(20, 0, 20)); + let mut actual_new_chunk = Chunk::new(IVec2::new(20, 20)); generator.generate_chunk(&mut actual_new_chunk); assert_eq!(possible_new_chunk.data, actual_new_chunk.data); diff --git a/src/server/terrain/resources.rs b/src/server/terrain/resources.rs index 1455bcb..c1cf178 100644 --- a/src/server/terrain/resources.rs +++ b/src/server/terrain/resources.rs @@ -9,11 +9,11 @@ use terrain_events::BlockUpdateEvent; #[derive(Resource, Default)] pub struct ClientChunkRequests { - queues: HashMap>, + queues: HashMap>, } impl ClientChunkRequests { - pub fn enqueue_bulk(&mut self, client_id: ClientId, chunk_positions: &mut VecDeque) { + pub fn enqueue_bulk(&mut self, client_id: ClientId, chunk_positions: &mut VecDeque) { self.queues .entry(client_id) .or_default() @@ -26,7 +26,7 @@ impl ClientChunkRequests { pub fn retain(&mut self, f: F) where - F: FnMut(&ClientId, &mut VecDeque) -> bool, + F: FnMut(&ClientId, &mut VecDeque) -> bool, { self.queues.retain(f) } diff --git a/src/server/terrain/systems.rs b/src/server/terrain/systems.rs index 9e26f5c..447926b 100644 --- a/src/server/terrain/systems.rs +++ b/src/server/terrain/systems.rs @@ -11,7 +11,7 @@ pub fn setup_world_system( info!("Generating chunks"); let mut chunks = - ChunkManager::instantiate_chunks(IVec3::ZERO, CONFIG.world.spawn_area_distance); + ChunkManager::instantiate_chunks(IVec2::ZERO, CONFIG.world.spawn_area_distance); chunks.par_iter_mut().for_each(|chunk| { info!("Generating chunk at {:?}", chunk.position); @@ -35,7 +35,7 @@ pub fn process_user_chunk_requests_system( } let take_count = min(MAX_REQUESTS_PER_CYCLE_PER_PLAYER, positions.len()); - let positions_to_process: Vec = positions.drain(0..take_count).collect(); + let positions_to_process: Vec = positions.drain(0..take_count).collect(); let (existing, generated): (Vec<_>, Vec<_>) = positions_to_process .into_iter() diff --git a/src/server/terrain/util/generator.rs b/src/server/terrain/util/generator.rs index 0be4e38..fa97572 100644 --- a/src/server/terrain/util/generator.rs +++ b/src/server/terrain/util/generator.rs @@ -26,7 +26,8 @@ macro_rules! for_each_chunk_coordinate { let chunk_origin = $chunk.position * CHUNK_SIZE as i32; let local_position = IVec3::new(x as i32, y as i32, z as i32); - let world_position = chunk_origin + local_position; + let world_position = + IVec3::new(chunk_origin[0], 0, chunk_origin[1]) + local_position; $body(x, y, z, world_position); } @@ -337,7 +338,7 @@ mod tests { #[test] fn test_generate_chunk() { let generator = Generator::default(); - let mut chunk = Chunk::new(IVec3::ZERO); + let mut chunk = Chunk::new(IVec2::ZERO); generator.generate_chunk(&mut chunk); diff --git a/src/shared/chunk/chunk_data.rs b/src/shared/chunk/chunk_data.rs index ae2e473..4a8c5f0 100644 --- a/src/shared/chunk/chunk_data.rs +++ b/src/shared/chunk/chunk_data.rs @@ -1,5 +1,6 @@ use std::hash::{DefaultHasher, Hash, Hasher}; +use bevy::math::IVec2; use bevy::math::IVec3; use crate::*; @@ -7,17 +8,17 @@ use crate::*; #[derive(Debug, Clone, Copy)] pub struct Chunk { pub data: [BlockId; CHUNK_LENGTH], - pub position: IVec3, + pub position: IVec2, } impl Default for Chunk { fn default() -> Self { - Self::new(IVec3::ZERO) + Self::new(IVec2::ZERO) } } impl Chunk { - pub fn new(position: IVec3) -> Self { + pub fn new(position: IVec2) -> Self { Self { position, data: [BlockId::Air; CHUNK_LENGTH], diff --git a/src/shared/chunk/manager.rs b/src/shared/chunk/manager.rs index c519c73..8250b57 100644 --- a/src/shared/chunk/manager.rs +++ b/src/shared/chunk/manager.rs @@ -1,12 +1,12 @@ use std::collections::HashMap; -use bevy::{log::info, math::IVec3, prelude::Resource}; +use bevy::{log::info, math::IVec2, math::IVec3, prelude::Resource}; use crate::*; #[derive(Resource)] pub struct ChunkManager { - pub chunks: HashMap, + pub chunks: HashMap, } impl Default for ChunkManager { @@ -23,7 +23,7 @@ impl ChunkManager { } pub fn with_chunks(chunks: Vec) -> Self { - let chunks: HashMap = chunks + let chunks: HashMap = chunks .into_iter() .map(|chunk| (chunk.position, chunk)) .collect(); @@ -31,27 +31,24 @@ impl ChunkManager { Self { chunks } } - pub fn instantiate_chunks(position: IVec3, render_distance: IVec3) -> Vec { - let render_distance_x = render_distance.x; - let render_distance_y = render_distance.y; - let render_distance_z = render_distance.z; + pub fn instantiate_chunks(position: IVec2, render_distance: IVec2) -> Vec { + let render_distance_x = render_distance[0]; + let render_distance_z = render_distance[1]; let mut chunks: Vec = Vec::new(); for x in -render_distance_x..render_distance_x { - for y in -render_distance_y..render_distance_y { - for z in -render_distance_z..render_distance_z { - let chunk_position = IVec3::new(x + position.x, y + position.y, z + position.z); - let chunk = Chunk::new(chunk_position); - chunks.push(chunk); - } + for z in -render_distance_z..render_distance_z { + let chunk_position = IVec2::new(x + position[0], z + position[1]); + let chunk = Chunk::new(chunk_position); + chunks.push(chunk); } } chunks } - pub fn sorted_new_chunk_positions(&self, origin: IVec3, distance: IVec3) -> Vec { + pub fn sorted_new_chunk_positions(&self, origin: IVec2, distance: IVec2) -> Vec { let all_positions = Self::get_sorted_chunk_positions_in_range(origin, distance); all_positions .into_iter() @@ -59,21 +56,17 @@ impl ChunkManager { .collect() } - pub fn get_sorted_chunk_positions_in_range(origin: IVec3, distance: IVec3) -> Vec { - let distance_x = distance.x; - let distance_y = distance.y; - let distance_z = distance.z; + pub fn get_sorted_chunk_positions_in_range(origin: IVec2, distance: IVec2) -> Vec { + let distance_x = distance[0]; + let distance_z = distance[1]; - let mut positions: Vec = Vec::with_capacity( - ((distance_x * 2 + 1) * (distance_y * 2 + 1) * (distance_z * 2 + 1)) as usize, - ); + let mut positions: Vec = + Vec::with_capacity(((distance_x * 2 + 1) * (distance_z * 2 + 1)) as usize); for x in -distance_x..=distance_x { - for y in -distance_y..=distance_y { - for z in -distance_z..=distance_z { - let chunk_position = IVec3::new(x + origin.x, y + origin.y, z + origin.z); - positions.push(chunk_position); - } + for z in -distance_z..=distance_z { + let chunk_position = IVec2::new(x + origin[0], z + origin[1]); + positions.push(chunk_position); } } @@ -96,31 +89,33 @@ impl ChunkManager { } } - pub fn set_chunk(&mut self, position: IVec3, chunk: Chunk) { + pub fn set_chunk(&mut self, position: IVec2, chunk: Chunk) { self.chunks.insert(position, chunk); } - pub fn get_chunk(&self, position: &IVec3) -> Option<&Chunk> { + pub fn get_chunk(&self, position: &IVec2) -> Option<&Chunk> { self.chunks.get(position) } - pub fn has_chunk(&self, position: &IVec3) -> bool { + pub fn has_chunk(&self, position: &IVec2) -> bool { self.chunks.contains_key(position) } - pub fn get_chunk_mut(&mut self, position: &IVec3) -> Option<&mut Chunk> { + pub fn get_chunk_mut(&mut self, position: &IVec2) -> Option<&mut Chunk> { self.chunks.get_mut(position) } - pub fn update_block(&mut self, position: IVec3, block: BlockId) -> Vec { + pub fn update_block(&mut self, position: IVec3, block: BlockId) -> Vec { Self::chunk_positions_containing_world_pos(position) .iter() .flat_map(|chunk_position| { - let chunk_option = self.get_chunk_mut(chunk_position); + let chunk_option = + self.get_chunk_mut(&IVec2::new(chunk_position[0], chunk_position[2])); match chunk_option { Some(chunk) => { let chunk_origin = *chunk_position * CHUNK_SIZE as i32; - let local_position = position - chunk_origin; + let local_position = + position - IVec3::new(chunk_origin[0], 0, chunk_origin[1]); info!("Performing local update at {:?}", local_position); @@ -146,8 +141,8 @@ impl ChunkManager { Some(chunk) => { let chunk_position = IVec3::new( chunk.position[0] * CHUNK_SIZE as i32, + position.y, chunk.position[1] * CHUNK_SIZE as i32, - chunk.position[2] * CHUNK_SIZE as i32, ); let local_position = position - chunk_position; Some(chunk.get(local_position.x, local_position.y, local_position.z)) @@ -159,7 +154,7 @@ impl ChunkManager { } } - fn chunk_positions_containing_world_pos(position: IVec3) -> Vec { + fn chunk_positions_containing_world_pos(position: IVec3) -> Vec { fn axis_chunks(world: i32) -> Vec { let size = CHUNK_SIZE as i32; let base = world.div_euclid(size); @@ -177,28 +172,24 @@ impl ChunkManager { } let xs = axis_chunks(position.x); - let ys = axis_chunks(position.y); let zs = axis_chunks(position.z); let mut out = Vec::new(); for x in xs { - for y in &ys { - for z in &zs { - out.push(IVec3::new(x, *y, *z)); - } + for z in &zs { + out.push(IVec2::new(x, *z)); } } out } - pub fn world_position_to_chunk_position(world_position: IVec3) -> IVec3 { - IVec3 { - x: world_position.x.div_euclid(CHUNK_SIZE as i32), - y: world_position.y.div_euclid(CHUNK_SIZE as i32), - z: world_position.z.div_euclid(CHUNK_SIZE as i32), - } + pub fn world_position_to_chunk_position(world_position: IVec3) -> IVec2 { + IVec2::new( + world_position.x.div_euclid(CHUNK_SIZE as i32), + world_position.z.div_euclid(CHUNK_SIZE as i32), + ) } fn chunk_at_position(&self, world_position: IVec3) -> Option<&Chunk> { @@ -206,10 +197,10 @@ impl ChunkManager { self.get_chunk(&chunk_position) } - pub fn get_all_chunk_positions(&self) -> Vec { + pub fn get_all_chunk_positions(&self) -> Vec { self.chunks .keys() - .map(|key| IVec3::new(key[0], key[1], key[2])) + .map(|key| IVec2::new(key[0], key[1])) .collect() } @@ -239,30 +230,30 @@ mod tests { assert_eq!( ChunkManager::chunk_positions_containing_world_pos(IVec3::ZERO), vec![ - IVec3::new(-1, -1, -1), - IVec3::new(-1, -1, 0), - IVec3::new(-1, 0, -1), - IVec3::new(-1, 0, 0), - IVec3::new(0, -1, -1), - IVec3::new(0, -1, 0), - IVec3::new(0, 0, -1), - IVec3::new(0, 0, 0) + IVec2::new(-1, -1), + IVec2::new(-1, 0), + IVec2::new(-1, -1), + IVec2::new(-1, 0), + IVec2::new(0, -1), + IVec2::new(0, -0), + IVec2::new(0, -1), + IVec2::new(0, 0) ] ); assert_eq!( ChunkManager::chunk_positions_containing_world_pos(IVec3::ONE), - vec![IVec3::new(0, 0, 0),] + vec![IVec2::new(0, 0),] ); assert_eq!( ChunkManager::chunk_positions_containing_world_pos(IVec3::new(0, 1, 1)), - vec![IVec3::new(-1, 0, 0), IVec3::new(0, 0, 0),] + vec![IVec2::new(-1, 0), IVec2::new(0, 0),] ); assert_eq!( ChunkManager::chunk_positions_containing_world_pos(IVec3::new(CHUNK_SIZE as i32, 1, 1)), - vec![IVec3::new(0, 0, 0), IVec3::new(1, 0, 0),] + vec![IVec2::new(0, 0), IVec2::new(1, 0),] ); } @@ -274,26 +265,25 @@ mod tests { #[test] fn test_instantiate_chunks() { - let position = IVec3::new(0, 0, 0); + let position = IVec2::new(0, 0); let width = 2; - let height = 3; let depth = 4; - let render_distance = IVec3::new(width, height, depth); + let render_distance = IVec2::new(width, depth); let chunks = ChunkManager::instantiate_chunks(position, render_distance); - assert_eq!(chunks.len(), (2 * width * 2 * height * 2 * depth) as usize,); + assert_eq!(chunks.len(), (2 * width * 2 * depth) as usize,); } #[test] fn test_insert_chunks() { let mut chunk_manager = ChunkManager::new(); - let position = IVec3::new(0, 0, 0); + let position = IVec2::new(0, 0); let render_distance = 2; let chunks = ChunkManager::instantiate_chunks( position, - IVec3::new(render_distance, render_distance, render_distance), + IVec2::new(render_distance, render_distance), ); let render_diameter = render_distance * 2; @@ -308,7 +298,7 @@ mod tests { #[test] fn test_set_and_get_chunk_mut() { let mut chunk_manager = ChunkManager::new(); - let position = IVec3::new(0, 0, 0); + let position = IVec2::ZERO; let chunk = Chunk::new(position); chunk_manager.set_chunk(position, chunk); @@ -319,7 +309,7 @@ mod tests { #[test] fn test_set_and_get_block() { let mut chunk_manager = ChunkManager::new(); - let position = IVec3::new(0, 0, 0); + let position = IVec2::ZERO; let chunk = Chunk::new(position); chunk_manager.set_chunk(position, chunk); @@ -334,9 +324,9 @@ mod tests { #[test] fn test_get_all_chunk_positions() { let mut chunk_manager = ChunkManager::new(); - chunk_manager.set_chunk(IVec3::new(0, 0, 0), Chunk::default()); - chunk_manager.set_chunk(IVec3::new(2, 0, 0), Chunk::default()); - chunk_manager.set_chunk(IVec3::new(1, 0, 3), Chunk::default()); + chunk_manager.set_chunk(IVec2::new(0, 0), Chunk::default()); + chunk_manager.set_chunk(IVec2::new(2, 0), Chunk::default()); + chunk_manager.set_chunk(IVec2::new(1, 3), Chunk::default()); let retrieved_chunk_positions = chunk_manager.get_all_chunk_positions(); assert_eq!(retrieved_chunk_positions.len(), 3); @@ -346,7 +336,7 @@ mod tests { #[rustfmt::skip] fn test_tallgrass_update() { let mut chunk_manager = ChunkManager::new(); - let chunk_position = IVec3::new(0, 0, 0); + let chunk_position = IVec2::ZERO; let chunk = Chunk::new(chunk_position); chunk_manager.set_chunk(chunk_position, chunk); diff --git a/src/shared/chunk_serializer.rs b/src/shared/chunk_serializer.rs index e6e6d06..e857da8 100644 --- a/src/shared/chunk_serializer.rs +++ b/src/shared/chunk_serializer.rs @@ -3,7 +3,7 @@ use crate::serialize_buffer; use crate::BlockId; use crate::Chunk; use crate::CHUNK_LENGTH; -use bevy::math::IVec3; +use bevy::math::IVec2; use serde::ser::SerializeStruct; use serde::{Deserialize, Serialize}; @@ -48,7 +48,7 @@ impl<'de> Deserialize<'de> for Chunk { #[derive(Deserialize)] struct ChunkData { data: BytesVec, - position: IVec3, + position: IVec2, } let ChunkData { data, position } = ChunkData::deserialize(deserializer)?; diff --git a/src/shared/networking.rs b/src/shared/networking.rs index c1eef07..928162d 100644 --- a/src/shared/networking.rs +++ b/src/shared/networking.rs @@ -5,7 +5,7 @@ use std::{ use bevy::{ ecs::resource::Resource, - math::{IVec3, Quat, Vec3}, + math::{IVec2, IVec3, Quat, Vec3}, }; use bevy_renet::netcode::NETCODE_USER_DATA_BYTES; use chrono::DateTime; @@ -211,7 +211,7 @@ pub enum NetworkingMessage { PlayerLeave(Username), PlayerUpdate(PlayerState), PlayerSync(HashMap), - ChunkBatchRequest(Vec), + ChunkBatchRequest(Vec), ChunkBatchResponse(Vec), ChatMessageSend(String), SingleChatMessageSync(ChatMessage), From d21a55f3bbdd26f24cb8abcf9001911db100d980 Mon Sep 17 00:00:00 2001 From: Daniel Bengl Date: Sun, 15 Feb 2026 08:54:27 +0100 Subject: [PATCH 03/15] Make unit tests pass --- src/shared/chunk/manager.rs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/shared/chunk/manager.rs b/src/shared/chunk/manager.rs index 8250b57..932285f 100644 --- a/src/shared/chunk/manager.rs +++ b/src/shared/chunk/manager.rs @@ -110,7 +110,7 @@ impl ChunkManager { .iter() .flat_map(|chunk_position| { let chunk_option = - self.get_chunk_mut(&IVec2::new(chunk_position[0], chunk_position[2])); + self.get_chunk_mut(&IVec2::new(chunk_position[0], chunk_position[1])); match chunk_option { Some(chunk) => { let chunk_origin = *chunk_position * CHUNK_SIZE as i32; @@ -141,7 +141,7 @@ impl ChunkManager { Some(chunk) => { let chunk_position = IVec3::new( chunk.position[0] * CHUNK_SIZE as i32, - position.y, + 0, chunk.position[1] * CHUNK_SIZE as i32, ); let local_position = position - chunk_position; @@ -232,10 +232,6 @@ mod tests { vec![ IVec2::new(-1, -1), IVec2::new(-1, 0), - IVec2::new(-1, -1), - IVec2::new(-1, 0), - IVec2::new(0, -1), - IVec2::new(0, -0), IVec2::new(0, -1), IVec2::new(0, 0) ] @@ -291,7 +287,7 @@ mod tests { chunk_manager.insert_chunks(chunks); assert_eq!( chunk_manager.chunks.len(), - (render_diameter * render_diameter * render_diameter) as usize + (render_diameter * render_diameter) as usize ); } @@ -313,7 +309,7 @@ mod tests { let chunk = Chunk::new(position); chunk_manager.set_chunk(position, chunk); - let block_position = IVec3::new(1, 1, 1); + let block_position = IVec3::ONE; let block_id = BlockId::Stone; chunk_manager.update_block(block_position, block_id); From 05c3a3a33a50ce47943074df959c26eec65e083e Mon Sep 17 00:00:00 2001 From: Daniel Bengl Date: Sun, 15 Feb 2026 09:24:52 +0100 Subject: [PATCH 04/15] Introduce CHUNK_HEIGHT, fix stack overflow error --- src/client/terrain/systems.rs | 2 +- src/server/terrain/persistence.rs | 2 +- src/server/terrain/systems.rs | 5 +++-- src/shared/chunk/chunk_data.rs | 21 +++++++++++---------- src/shared/chunk/constants.rs | 3 ++- src/shared/chunk/manager.rs | 7 ++----- src/shared/chunk_serializer.rs | 2 +- 7 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/client/terrain/systems.rs b/src/client/terrain/systems.rs index 2e8d139..2f2f480 100644 --- a/src/client/terrain/systems.rs +++ b/src/client/terrain/systems.rs @@ -131,8 +131,8 @@ pub fn handle_chunk_rerequests_system( fn create_mesh_task(chunk: &Chunk, texture_manager: &terrain_util::TextureManager) -> MeshTask { let task_pool = AsyncComputeTaskPool::get(); - let chunk = *chunk; let texture_manager = texture_manager.clone(); + let chunk = chunk.clone(); MeshTask(task_pool.spawn(async move { ChunkMeshes { cube_mesh: terrain_util::create_cube_mesh_for_chunk(&chunk, &texture_manager), diff --git a/src/server/terrain/persistence.rs b/src/server/terrain/persistence.rs index 80af382..642ce99 100644 --- a/src/server/terrain/persistence.rs +++ b/src/server/terrain/persistence.rs @@ -72,7 +72,7 @@ fn build_world_save_from_resources( chunk_manager: &ChunkManager, generator: &Generator, ) -> WorldSave { - let chunks = chunk_manager.all_chunks().into_iter().copied().collect(); + let chunks = chunk_manager.all_chunks().into_iter().cloned().collect(); let generator = generator.clone(); WorldSave { diff --git a/src/server/terrain/systems.rs b/src/server/terrain/systems.rs index 447926b..a4fd968 100644 --- a/src/server/terrain/systems.rs +++ b/src/server/terrain/systems.rs @@ -44,10 +44,11 @@ pub fn process_user_chunk_requests_system( let existing_chunks: Vec = existing .into_iter() .map(|pos| { - *chunk_manager + chunk_manager .get_chunk(&pos) .expect("Chunk must exist, as it is inside the 'existing' partition") }) + .cloned() .collect(); let generated_chunks: Vec = generated @@ -60,7 +61,7 @@ pub fn process_user_chunk_requests_system( .collect(); for chunk in &generated_chunks { - chunk_manager.insert_chunk(*chunk); + chunk_manager.insert_chunk(chunk.clone()); } let chunks: Vec = existing_chunks diff --git a/src/shared/chunk/chunk_data.rs b/src/shared/chunk/chunk_data.rs index 4a8c5f0..8341efe 100644 --- a/src/shared/chunk/chunk_data.rs +++ b/src/shared/chunk/chunk_data.rs @@ -5,9 +5,9 @@ use bevy::math::IVec3; use crate::*; -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone)] pub struct Chunk { - pub data: [BlockId; CHUNK_LENGTH], + pub data: Box<[BlockId; CHUNK_LENGTH]>, pub position: IVec2, } @@ -21,12 +21,12 @@ impl Chunk { pub fn new(position: IVec2) -> Self { Self { position, - data: [BlockId::Air; CHUNK_LENGTH], + data: Box::new([BlockId::Air; CHUNK_LENGTH]), } } pub fn valid_local(x: usize, y: usize, z: usize) -> bool { - x < CHUNK_SIZE && y < CHUNK_SIZE && z < CHUNK_SIZE + x < CHUNK_SIZE && y < CHUNK_HEIGHT && z < CHUNK_SIZE } pub fn is_within_padded_bounds(x: i32, y: i32, z: i32) -> bool { @@ -34,17 +34,17 @@ impl Chunk { && y >= -1 && z >= -1 && x <= CHUNK_SIZE as i32 - && y <= CHUNK_SIZE as i32 + && y <= CHUNK_HEIGHT as i32 && z <= CHUNK_SIZE as i32 } pub fn valid_unpadded(x: usize, y: usize, z: usize) -> bool { - x < PADDED_CHUNK_SIZE && y < PADDED_CHUNK_SIZE && z < PADDED_CHUNK_SIZE + x < PADDED_CHUNK_SIZE && y < CHUNK_HEIGHT && z < PADDED_CHUNK_SIZE } pub fn get(&self, x: i32, y: i32, z: i32) -> BlockId { assert!(Self::is_within_padded_bounds(x, y, z)); - self.get_unpadded((x + 1) as usize, (y + 1) as usize, (z + 1) as usize) + self.get_unpadded((x + 1) as usize, y as usize, (z + 1) as usize) } pub fn get_unpadded(&self, x: usize, y: usize, z: usize) -> BlockId { @@ -53,7 +53,7 @@ impl Chunk { pub fn set(&mut self, x: i32, y: i32, z: i32, value: BlockId) { assert!(Self::is_within_padded_bounds(x, y, z)); - self.set_unpadded((x + 1) as usize, (y + 1) as usize, (z + 1) as usize, value); + self.set_unpadded((x + 1) as usize, y as usize, (z + 1) as usize, value); } pub fn update(&mut self, x: i32, y: i32, z: i32, value: BlockId) { @@ -74,9 +74,10 @@ impl Chunk { #[rustfmt::skip] pub fn index(x: usize, y: usize, z: usize) -> usize { let n = PADDED_CHUNK_SIZE; - assert!(x < n && y < n && z < n, "Index out of bounds: ({}, {}, {})", x,y,z); + let h = CHUNK_HEIGHT; + assert!(x < n && y < h && z < n, "Index out of bounds: ({}, {}, {})", x,y,z); - z + n * (y + n * x) + z + n * (y + h * x) } pub fn key_eq_pos(key: [i32; 3], position: IVec3) -> bool { diff --git a/src/shared/chunk/constants.rs b/src/shared/chunk/constants.rs index de465ea..f91721c 100644 --- a/src/shared/chunk/constants.rs +++ b/src/shared/chunk/constants.rs @@ -1,3 +1,4 @@ pub const CHUNK_SIZE: usize = 32; +pub const CHUNK_HEIGHT: usize = 256; pub const PADDED_CHUNK_SIZE: usize = CHUNK_SIZE + 2; -pub const CHUNK_LENGTH: usize = PADDED_CHUNK_SIZE * PADDED_CHUNK_SIZE * PADDED_CHUNK_SIZE; +pub const CHUNK_LENGTH: usize = PADDED_CHUNK_SIZE * CHUNK_HEIGHT * PADDED_CHUNK_SIZE; diff --git a/src/shared/chunk/manager.rs b/src/shared/chunk/manager.rs index 932285f..db1bd6c 100644 --- a/src/shared/chunk/manager.rs +++ b/src/shared/chunk/manager.rs @@ -198,10 +198,7 @@ impl ChunkManager { } pub fn get_all_chunk_positions(&self) -> Vec { - self.chunks - .keys() - .map(|key| IVec2::new(key[0], key[1])) - .collect() + self.chunks.keys().copied().collect() } pub fn all_chunks(&self) -> Vec<&Chunk> { @@ -297,7 +294,7 @@ mod tests { let position = IVec2::ZERO; let chunk = Chunk::new(position); - chunk_manager.set_chunk(position, chunk); + chunk_manager.set_chunk(position, chunk.clone()); let retrieved_chunk = chunk_manager.get_chunk_mut(&position).unwrap(); assert_eq!(retrieved_chunk.position, chunk.position); } diff --git a/src/shared/chunk_serializer.rs b/src/shared/chunk_serializer.rs index e857da8..8d07cc9 100644 --- a/src/shared/chunk_serializer.rs +++ b/src/shared/chunk_serializer.rs @@ -63,7 +63,7 @@ impl<'de> Deserialize<'de> for Chunk { .map_err(|_| serde::de::Error::custom("Failed to convert data to BlockId array"))?; Ok(Chunk { - data: data_as_block_id, + data: Box::new(data_as_block_id), position, }) } From b845fc10c18f09d375888f9e197f4d7d4edb1a9b Mon Sep 17 00:00:00 2001 From: Daniel Bengl Date: Sun, 15 Feb 2026 09:48:53 +0100 Subject: [PATCH 05/15] Update CHUNK_HEIGHT referencs --- src/client/terrain/util/cross_mesher.rs | 2 +- src/client/terrain/util/cube_mesher.rs | 13 +++++++++---- src/server/terrain/util/generator.rs | 10 ++-------- src/shared/chunk/chunk_data.rs | 12 ++++++++++-- src/shared/chunk/manager.rs | 4 ++-- 5 files changed, 24 insertions(+), 17 deletions(-) diff --git a/src/client/terrain/util/cross_mesher.rs b/src/client/terrain/util/cross_mesher.rs index db571e1..a4a458f 100644 --- a/src/client/terrain/util/cross_mesher.rs +++ b/src/client/terrain/util/cross_mesher.rs @@ -26,7 +26,7 @@ fn create_cross_geometry_for_chunk( let mut index_offset = 0; for x in 0..CHUNK_SIZE { - for y in 0..CHUNK_SIZE { + for y in 0..CHUNK_HEIGHT { for z in 0..CHUNK_SIZE { let block_id = chunk.get(x as i32, y as i32, z as i32); let pos = Vec3::new(x as f32, y as f32, z as f32); diff --git a/src/client/terrain/util/cube_mesher.rs b/src/client/terrain/util/cube_mesher.rs index 0937f6d..3044679 100644 --- a/src/client/terrain/util/cube_mesher.rs +++ b/src/client/terrain/util/cube_mesher.rs @@ -64,7 +64,7 @@ pub fn create_cube_mesh_for_chunk(chunk: &Chunk, texture_manager: &TextureManage }; for x in 1..CHUNK_SIZE + 1 { - for y in 1..CHUNK_SIZE + 1 { + for y in 0..CHUNK_HEIGHT { for z in 1..CHUNK_SIZE + 1 { let block_id = chunk.get_unpadded(x, y, z); @@ -89,8 +89,13 @@ pub fn create_cube_mesh_for_chunk(chunk: &Chunk, texture_manager: &TextureManage let mut mask = 0b000000; - update_mask(chunk, &mut mask, 0b000001, x, y + 1, z); - update_mask(chunk, &mut mask, 0b000010, x, y - 1, z); + if y > 0 { + update_mask(chunk, &mut mask, 0b000010, x, y - 1, z); + } + + if y < CHUNK_HEIGHT { + update_mask(chunk, &mut mask, 0b000001, x, y + 1, z); + } update_mask(chunk, &mut mask, 0b000100, x + 1, y, z); update_mask(chunk, &mut mask, 0b001000, x - 1, y, z); @@ -100,7 +105,7 @@ pub fn create_cube_mesh_for_chunk(chunk: &Chunk, texture_manager: &TextureManage let cube_data = create_cube_geometry_data( (x - 1) as f32, - (y - 1) as f32, + (y) as f32, (z - 1) as f32, mask, block_id, diff --git a/src/server/terrain/util/generator.rs b/src/server/terrain/util/generator.rs index fa97572..d047ff9 100644 --- a/src/server/terrain/util/generator.rs +++ b/src/server/terrain/util/generator.rs @@ -11,16 +11,10 @@ use crate::{ macro_rules! for_each_chunk_coordinate { ($chunk:expr, $body:expr) => { for x in 0..CHUNK_SIZE + 2 { - for y in 0..CHUNK_SIZE + 2 { + for y in 0..CHUNK_HEIGHT { for z in 0..CHUNK_SIZE + 2 { #[cfg(feature = "skip_chunk_padding")] - if x == 0 - || x == CHUNK_SIZE + 1 - || y == 0 - || y == CHUNK_SIZE + 1 - || z == 0 - || z == CHUNK_SIZE + 1 - { + if false || x == 0 || x == CHUNK_SIZE + 1 || z == 0 || z == CHUNK_SIZE + 1 { continue; } diff --git a/src/shared/chunk/chunk_data.rs b/src/shared/chunk/chunk_data.rs index 8341efe..23ec442 100644 --- a/src/shared/chunk/chunk_data.rs +++ b/src/shared/chunk/chunk_data.rs @@ -31,10 +31,10 @@ impl Chunk { pub fn is_within_padded_bounds(x: i32, y: i32, z: i32) -> bool { x >= -1 - && y >= -1 + && y >= 0 && z >= -1 && x <= CHUNK_SIZE as i32 - && y <= CHUNK_HEIGHT as i32 + && y < CHUNK_HEIGHT as i32 && z <= CHUNK_SIZE as i32 } @@ -47,6 +47,14 @@ impl Chunk { self.get_unpadded((x + 1) as usize, y as usize, (z + 1) as usize) } + pub fn get_safe(&self, x: i32, y: i32, z: i32) -> Option { + if Self::is_within_padded_bounds(x, y, z) { + Some(self.get_unpadded((x + 1) as usize, y as usize, (z + 1) as usize)) + } else { + None + } + } + pub fn get_unpadded(&self, x: usize, y: usize, z: usize) -> BlockId { self.data[Self::index(x, y, z)] } diff --git a/src/shared/chunk/manager.rs b/src/shared/chunk/manager.rs index db1bd6c..7a06e8e 100644 --- a/src/shared/chunk/manager.rs +++ b/src/shared/chunk/manager.rs @@ -120,7 +120,7 @@ impl ChunkManager { info!("Performing local update at {:?}", local_position); assert!(local_position.x >= -1 && local_position.x <= CHUNK_SIZE as i32); - assert!(local_position.y >= -1 && local_position.y <= CHUNK_SIZE as i32); + assert!(local_position.y >= 0 && local_position.y < CHUNK_HEIGHT as i32); assert!(local_position.z >= -1 && local_position.z <= CHUNK_SIZE as i32); chunk.update(local_position.x, local_position.y, local_position.z, block); @@ -145,7 +145,7 @@ impl ChunkManager { chunk.position[1] * CHUNK_SIZE as i32, ); let local_position = position - chunk_position; - Some(chunk.get(local_position.x, local_position.y, local_position.z)) + chunk.get_safe(local_position.x, local_position.y, local_position.z) } None => { // println!("No chunk found for block at {:?}", position); From 277ac2143d28fc299b1e6c489f371b97ec0f26b5 Mon Sep 17 00:00:00 2001 From: Daniel Bengl Date: Sun, 15 Feb 2026 10:42:13 +0100 Subject: [PATCH 06/15] Fix out of world panic f --- src/client/player/systems/terrain.rs | 4 ++++ src/shared/chunk/manager.rs | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/src/client/player/systems/terrain.rs b/src/client/player/systems/terrain.rs index acdc89e..a4865e8 100644 --- a/src/client/player/systems/terrain.rs +++ b/src/client/player/systems/terrain.rs @@ -9,6 +9,10 @@ pub fn handle_block_update_events( ) { for event in block_update_events.read() { info!("Block update message: {:?}", event.position); + if !ChunkManager::inside_world(&event.position) { + warn!("Player attempted to set block outside of the world"); + continue; + } chunk_manager .update_block(event.position, event.block) .iter() diff --git a/src/shared/chunk/manager.rs b/src/shared/chunk/manager.rs index 7a06e8e..14706cc 100644 --- a/src/shared/chunk/manager.rs +++ b/src/shared/chunk/manager.rs @@ -105,6 +105,10 @@ impl ChunkManager { self.chunks.get_mut(position) } + pub fn inside_world(position: &IVec3) -> bool { + position.y >= 0 && position.y < CHUNK_HEIGHT as i32 + } + pub fn update_block(&mut self, position: IVec3, block: BlockId) -> Vec { Self::chunk_positions_containing_world_pos(position) .iter() From ddc2f392b3c7347434a1873bd52942720e6e34aa Mon Sep 17 00:00:00 2001 From: Daniel Bengl Date: Sun, 15 Feb 2026 10:42:24 +0100 Subject: [PATCH 07/15] Update mesher --- src/client/terrain/util/cube_mesher.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/client/terrain/util/cube_mesher.rs b/src/client/terrain/util/cube_mesher.rs index 3044679..607821e 100644 --- a/src/client/terrain/util/cube_mesher.rs +++ b/src/client/terrain/util/cube_mesher.rs @@ -89,12 +89,16 @@ pub fn create_cube_mesh_for_chunk(chunk: &Chunk, texture_manager: &TextureManage let mut mask = 0b000000; - if y > 0 { + if y >= 1 { update_mask(chunk, &mut mask, 0b000010, x, y - 1, z); + } else { + // Don't render faces at bottom of world } - if y < CHUNK_HEIGHT { + if y < CHUNK_HEIGHT - 1 { update_mask(chunk, &mut mask, 0b000001, x, y + 1, z); + } else { + mask |= 0b000001 } update_mask(chunk, &mut mask, 0b000100, x + 1, y, z); From 94942c87b6a83718c4fbd713d19cbfc02cecdafb Mon Sep 17 00:00:00 2001 From: Daniel Bengl Date: Sun, 15 Feb 2026 12:01:32 +0100 Subject: [PATCH 08/15] Reduce height --- src/shared/chunk/constants.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shared/chunk/constants.rs b/src/shared/chunk/constants.rs index f91721c..f40d658 100644 --- a/src/shared/chunk/constants.rs +++ b/src/shared/chunk/constants.rs @@ -1,4 +1,4 @@ pub const CHUNK_SIZE: usize = 32; -pub const CHUNK_HEIGHT: usize = 256; +pub const CHUNK_HEIGHT: usize = 128; pub const PADDED_CHUNK_SIZE: usize = CHUNK_SIZE + 2; pub const CHUNK_LENGTH: usize = PADDED_CHUNK_SIZE * CHUNK_HEIGHT * PADDED_CHUNK_SIZE; From b414cb1259003f51f670fc8f4856a8164af089ff Mon Sep 17 00:00:00 2001 From: Daniel Bengl Date: Sun, 15 Feb 2026 12:45:14 +0100 Subject: [PATCH 09/15] IVec2 -> ChunkPosition --- src/client/collider/systems.rs | 2 +- src/client/player/resources.rs | 4 +- src/client/terrain/events.rs | 8 +-- src/client/terrain/resources.rs | 20 +++--- src/client/terrain/systems.rs | 6 +- src/server/terrain/persistence.rs | 6 +- src/server/terrain/resources.rs | 10 ++- src/server/terrain/systems.rs | 4 +- src/server/terrain/util/generator.rs | 2 +- src/shared/chunk/chunk_data.rs | 88 +++++++++++++++++++++++++- src/shared/chunk/manager.rs | 92 +++++++++++++++------------- src/shared/chunk_serializer.rs | 4 +- src/shared/networking.rs | 6 +- 13 files changed, 171 insertions(+), 81 deletions(-) diff --git a/src/client/collider/systems.rs b/src/client/collider/systems.rs index 43f3ac9..3e8ab8e 100644 --- a/src/client/collider/systems.rs +++ b/src/client/collider/systems.rs @@ -127,7 +127,7 @@ mod tests { let block = BlockId::Dirt; let mut resource = app.world_mut().get_resource_mut::().unwrap(); - let chunks = ChunkManager::instantiate_chunks(IVec2::ZERO, IVec2::ONE); + let chunks = ChunkManager::instantiate_chunks(ChunkPosition::ZERO, IVec2::ONE); resource.insert_chunks(chunks); resource.update_block(IVec3 { x: 6, y: 7, z: 8 }, block); diff --git a/src/client/player/resources.rs b/src/client/player/resources.rs index 7f6ae92..b205b41 100644 --- a/src/client/player/resources.rs +++ b/src/client/player/resources.rs @@ -48,7 +48,7 @@ impl LastPlayerPosition { Self(IVec3::ZERO) } - pub fn chunk_position(&self) -> IVec2 { + pub fn chunk_position(&self) -> ChunkPosition { Self::chunk_pos(self.0) } @@ -56,7 +56,7 @@ impl LastPlayerPosition { Self::chunk_pos(self.0) == Self::chunk_pos(other_world_position) } - fn chunk_pos(world_pos: IVec3) -> IVec2 { + fn chunk_pos(world_pos: IVec3) -> ChunkPosition { ChunkManager::world_position_to_chunk_position(world_pos) } } diff --git a/src/client/terrain/events.rs b/src/client/terrain/events.rs index 7b57445..673830e 100644 --- a/src/client/terrain/events.rs +++ b/src/client/terrain/events.rs @@ -2,22 +2,22 @@ use crate::prelude::*; #[derive(Message)] pub struct ChunkMeshUpdateEvent { - pub chunk_position: IVec2, + pub chunk_position: ChunkPosition, } #[derive(Message)] pub struct RerequestChunks { - pub center_chunk_position: IVec2, + pub center_chunk_position: ChunkPosition, } #[derive(Message)] pub struct RequestChunkBatch { - pub positions: Vec, + pub positions: Vec, } #[derive(Message)] pub struct CleanupChunksAroundOrigin { - pub center_chunk_position: IVec2, + pub center_chunk_position: ChunkPosition, } #[derive(Message)] diff --git a/src/client/terrain/resources.rs b/src/client/terrain/resources.rs index 03394a9..fc48300 100644 --- a/src/client/terrain/resources.rs +++ b/src/client/terrain/resources.rs @@ -15,7 +15,7 @@ impl SpawnRegionLoaded { #[derive(Resource, Default)] pub struct RequestedChunks { - pub previous_chunks: HashSet, + pub previous_chunks: HashSet, } #[derive(Eq, Hash, Clone, PartialEq)] @@ -31,7 +31,7 @@ pub struct ChunkMeshes { pub struct MeshTask(pub Task); pub struct FutureChunkMesh { - pub position: IVec2, + pub position: ChunkPosition, pub meshes_task: MeshTask, } @@ -42,12 +42,12 @@ pub struct MesherTasks { #[derive(Resource, Default)] pub struct ChunkEntityMap { - map: HashMap>, + map: HashMap>, } #[derive(Resource, Default)] pub struct SpawnRegion { - pub origin_chunk_position: IVec2, + pub origin_chunk_position: ChunkPosition, } impl SpawnRegion { @@ -63,23 +63,23 @@ impl ChunkEntityMap { self.map.len() } - pub fn add(&mut self, chunk_position: IVec2, entity: Entity) { + pub fn add(&mut self, chunk_position: ChunkPosition, entity: Entity) { self.map.entry(chunk_position).or_default().push(entity); } - pub fn remove(&mut self, chunk_position: IVec2) -> Option> { + pub fn remove(&mut self, chunk_position: ChunkPosition) -> Option> { self.map.remove(&chunk_position) } pub fn extract_outside_distance( &mut self, - origin: &IVec2, + origin: &ChunkPosition, distance: &IVec2, - ) -> Vec<(IVec2, Vec)> { - let extracted: HashMap> = self + ) -> Vec<(ChunkPosition, Vec)> { + let extracted: HashMap> = self .map .extract_if(|k, _v| { - (k[0] - origin[0]).abs() > distance[0] || (k[1] - origin[1]).abs() > distance[1] + (k.x - origin.x).abs() > distance[0] || (k.z - origin.z).abs() > distance[1] }) .collect(); diff --git a/src/client/terrain/systems.rs b/src/client/terrain/systems.rs index 2f2f480..185897b 100644 --- a/src/client/terrain/systems.rs +++ b/src/client/terrain/systems.rs @@ -60,7 +60,7 @@ pub fn handle_chunk_request_chunk_batch_event_system( return; } - let mut new_positions: HashSet = HashSet::new(); + let mut new_positions: HashSet = HashSet::new(); for batch_event in batch_events.read() { batch_event.positions.iter().for_each(|position| { new_positions.insert(*position); @@ -68,8 +68,8 @@ pub fn handle_chunk_request_chunk_batch_event_system( } let old_positions = &all_requests.previous_chunks; - let diff: HashSet<&IVec2> = new_positions.difference(old_positions).collect(); - let diff: Vec = diff.into_iter().copied().collect(); + let diff: HashSet<&ChunkPosition> = new_positions.difference(old_positions).collect(); + let diff: Vec = diff.into_iter().copied().collect(); let batched_positions = diff.chunks(32); diff --git a/src/server/terrain/persistence.rs b/src/server/terrain/persistence.rs index 642ce99..6f35bc2 100644 --- a/src/server/terrain/persistence.rs +++ b/src/server/terrain/persistence.rs @@ -147,7 +147,7 @@ mod tests { generator.params.density.squash_factor = 6.7; let mut chunk_manager = ChunkManager::new(); - let mut chunks = ChunkManager::instantiate_chunks(IVec2::ZERO, IVec2::ONE); + let mut chunks = ChunkManager::instantiate_chunks(ChunkPosition::ZERO, IVec2::ONE); assert!(!chunks.is_empty()); @@ -168,7 +168,7 @@ mod tests { #[test] fn test_world_continuation() { let generator = Generator::with_seed(0); - let mut possible_new_chunk = Chunk::new(IVec2::new(20, 20)); + let mut possible_new_chunk = Chunk::new(ChunkPosition::new(20, 20)); generator.generate_chunk(&mut possible_new_chunk); save_world("my_world", &ChunkManager::new(), &generator).unwrap(); @@ -176,7 +176,7 @@ mod tests { let world = read_world_save_by_name("my_world").unwrap(); let generator = world.generator; - let mut actual_new_chunk = Chunk::new(IVec2::new(20, 20)); + let mut actual_new_chunk = Chunk::new(ChunkPosition::new(20, 20)); generator.generate_chunk(&mut actual_new_chunk); assert_eq!(possible_new_chunk.data, actual_new_chunk.data); diff --git a/src/server/terrain/resources.rs b/src/server/terrain/resources.rs index c1cf178..f2c5493 100644 --- a/src/server/terrain/resources.rs +++ b/src/server/terrain/resources.rs @@ -9,11 +9,15 @@ use terrain_events::BlockUpdateEvent; #[derive(Resource, Default)] pub struct ClientChunkRequests { - queues: HashMap>, + queues: HashMap>, } impl ClientChunkRequests { - pub fn enqueue_bulk(&mut self, client_id: ClientId, chunk_positions: &mut VecDeque) { + pub fn enqueue_bulk( + &mut self, + client_id: ClientId, + chunk_positions: &mut VecDeque, + ) { self.queues .entry(client_id) .or_default() @@ -26,7 +30,7 @@ impl ClientChunkRequests { pub fn retain(&mut self, f: F) where - F: FnMut(&ClientId, &mut VecDeque) -> bool, + F: FnMut(&ClientId, &mut VecDeque) -> bool, { self.queues.retain(f) } diff --git a/src/server/terrain/systems.rs b/src/server/terrain/systems.rs index a4fd968..36c8fc1 100644 --- a/src/server/terrain/systems.rs +++ b/src/server/terrain/systems.rs @@ -11,7 +11,7 @@ pub fn setup_world_system( info!("Generating chunks"); let mut chunks = - ChunkManager::instantiate_chunks(IVec2::ZERO, CONFIG.world.spawn_area_distance); + ChunkManager::instantiate_chunks(ChunkPosition::ZERO, CONFIG.world.spawn_area_distance); chunks.par_iter_mut().for_each(|chunk| { info!("Generating chunk at {:?}", chunk.position); @@ -35,7 +35,7 @@ pub fn process_user_chunk_requests_system( } let take_count = min(MAX_REQUESTS_PER_CYCLE_PER_PLAYER, positions.len()); - let positions_to_process: Vec = positions.drain(0..take_count).collect(); + let positions_to_process: Vec = positions.drain(0..take_count).collect(); let (existing, generated): (Vec<_>, Vec<_>) = positions_to_process .into_iter() diff --git a/src/server/terrain/util/generator.rs b/src/server/terrain/util/generator.rs index d047ff9..0decf8b 100644 --- a/src/server/terrain/util/generator.rs +++ b/src/server/terrain/util/generator.rs @@ -332,7 +332,7 @@ mod tests { #[test] fn test_generate_chunk() { let generator = Generator::default(); - let mut chunk = Chunk::new(IVec2::ZERO); + let mut chunk = Chunk::new(ChunkPosition::ZERO); generator.generate_chunk(&mut chunk); diff --git a/src/shared/chunk/chunk_data.rs b/src/shared/chunk/chunk_data.rs index 23ec442..0c51dde 100644 --- a/src/shared/chunk/chunk_data.rs +++ b/src/shared/chunk/chunk_data.rs @@ -2,23 +2,105 @@ use std::hash::{DefaultHasher, Hash, Hasher}; use bevy::math::IVec2; use bevy::math::IVec3; +use serde::Deserialize; +use serde::Serialize; use crate::*; +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Default)] +pub struct ChunkPosition { + pub x: i32, + pub z: i32, +} + +impl std::ops::Add for ChunkPosition { + type Output = ChunkPosition; + + fn add(self, rhs: Self) -> Self::Output { + Self { + x: self.x + rhs.x, + z: self.z + rhs.z, + } + } +} + +impl std::ops::Sub for ChunkPosition { + type Output = ChunkPosition; + + fn sub(self, rhs: Self) -> Self::Output { + Self { + x: self.x - rhs.x, + z: self.z - rhs.z, + } + } +} + +impl ChunkPosition { + pub const ZERO: Self = Self { x: 0, z: 0 }; + pub const ONE: Self = Self { x: 1, z: 1 }; + + pub fn new(x: i32, z: i32) -> Self { + Self { x, z } + } + + pub fn length_squared(self) -> i32 { + self.x * self.x + self.z * self.z + } + + pub fn as_vec2(self) -> bevy::math::Vec2 { + bevy::math::Vec2::new(self.x as f32, self.z as f32) + } +} + +impl std::ops::Index for ChunkPosition { + type Output = i32; + + fn index(&self, index: usize) -> &Self::Output { + match index { + 0 => &self.x, + 1 => &self.z, + _ => panic!("Index out of bounds for ChunkPosition: {}", index), + } + } +} + +impl std::ops::Mul for ChunkPosition { + type Output = ChunkPosition; + + fn mul(self, rhs: i32) -> Self::Output { + Self { + x: self.x * rhs, + z: self.z * rhs, + } + } +} + +impl From for ChunkPosition { + fn from(v: IVec2) -> Self { + Self { x: v.x, z: v.y } + } +} + +impl From for IVec2 { + fn from(p: ChunkPosition) -> Self { + IVec2::new(p.x, p.z) + } +} + #[derive(Debug, Clone)] pub struct Chunk { pub data: Box<[BlockId; CHUNK_LENGTH]>, - pub position: IVec2, + pub position: ChunkPosition, } impl Default for Chunk { fn default() -> Self { - Self::new(IVec2::ZERO) + Self::new(ChunkPosition::ZERO) } } impl Chunk { - pub fn new(position: IVec2) -> Self { + pub fn new(position: ChunkPosition) -> Self { Self { position, data: Box::new([BlockId::Air; CHUNK_LENGTH]), diff --git a/src/shared/chunk/manager.rs b/src/shared/chunk/manager.rs index 14706cc..8a9e701 100644 --- a/src/shared/chunk/manager.rs +++ b/src/shared/chunk/manager.rs @@ -6,7 +6,7 @@ use crate::*; #[derive(Resource)] pub struct ChunkManager { - pub chunks: HashMap, + pub chunks: HashMap, } impl Default for ChunkManager { @@ -23,7 +23,7 @@ impl ChunkManager { } pub fn with_chunks(chunks: Vec) -> Self { - let chunks: HashMap = chunks + let chunks: HashMap = chunks .into_iter() .map(|chunk| (chunk.position, chunk)) .collect(); @@ -31,7 +31,7 @@ impl ChunkManager { Self { chunks } } - pub fn instantiate_chunks(position: IVec2, render_distance: IVec2) -> Vec { + pub fn instantiate_chunks(position: ChunkPosition, render_distance: IVec2) -> Vec { let render_distance_x = render_distance[0]; let render_distance_z = render_distance[1]; @@ -39,7 +39,7 @@ impl ChunkManager { for x in -render_distance_x..render_distance_x { for z in -render_distance_z..render_distance_z { - let chunk_position = IVec2::new(x + position[0], z + position[1]); + let chunk_position = position + ChunkPosition::new(x, z); let chunk = Chunk::new(chunk_position); chunks.push(chunk); } @@ -48,7 +48,11 @@ impl ChunkManager { chunks } - pub fn sorted_new_chunk_positions(&self, origin: IVec2, distance: IVec2) -> Vec { + pub fn sorted_new_chunk_positions( + &self, + origin: ChunkPosition, + distance: IVec2, + ) -> Vec { let all_positions = Self::get_sorted_chunk_positions_in_range(origin, distance); all_positions .into_iter() @@ -56,24 +60,27 @@ impl ChunkManager { .collect() } - pub fn get_sorted_chunk_positions_in_range(origin: IVec2, distance: IVec2) -> Vec { + pub fn get_sorted_chunk_positions_in_range( + origin: ChunkPosition, + distance: IVec2, + ) -> Vec { let distance_x = distance[0]; let distance_z = distance[1]; - let mut positions: Vec = + let mut positions: Vec = Vec::with_capacity(((distance_x * 2 + 1) * (distance_z * 2 + 1)) as usize); for x in -distance_x..=distance_x { for z in -distance_z..=distance_z { - let chunk_position = IVec2::new(x + origin[0], z + origin[1]); + let chunk_position = ChunkPosition::new(x + origin.x, z + origin.z); positions.push(chunk_position); } } positions.sort_by(|a, b| { - (a - origin) + (*a - origin) .length_squared() - .cmp(&(b - origin).length_squared()) + .cmp(&(*b - origin).length_squared()) }); positions @@ -89,19 +96,19 @@ impl ChunkManager { } } - pub fn set_chunk(&mut self, position: IVec2, chunk: Chunk) { + pub fn set_chunk(&mut self, position: ChunkPosition, chunk: Chunk) { self.chunks.insert(position, chunk); } - pub fn get_chunk(&self, position: &IVec2) -> Option<&Chunk> { + pub fn get_chunk(&self, position: &ChunkPosition) -> Option<&Chunk> { self.chunks.get(position) } - pub fn has_chunk(&self, position: &IVec2) -> bool { + pub fn has_chunk(&self, position: &ChunkPosition) -> bool { self.chunks.contains_key(position) } - pub fn get_chunk_mut(&mut self, position: &IVec2) -> Option<&mut Chunk> { + pub fn get_chunk_mut(&mut self, position: &ChunkPosition) -> Option<&mut Chunk> { self.chunks.get_mut(position) } @@ -109,17 +116,16 @@ impl ChunkManager { position.y >= 0 && position.y < CHUNK_HEIGHT as i32 } - pub fn update_block(&mut self, position: IVec3, block: BlockId) -> Vec { + pub fn update_block(&mut self, position: IVec3, block: BlockId) -> Vec { Self::chunk_positions_containing_world_pos(position) .iter() .flat_map(|chunk_position| { - let chunk_option = - self.get_chunk_mut(&IVec2::new(chunk_position[0], chunk_position[1])); + let chunk_option = self.get_chunk_mut(chunk_position); match chunk_option { Some(chunk) => { let chunk_origin = *chunk_position * CHUNK_SIZE as i32; let local_position = - position - IVec3::new(chunk_origin[0], 0, chunk_origin[1]); + position - IVec3::new(chunk_origin.x, 0, chunk_origin.z); info!("Performing local update at {:?}", local_position); @@ -131,10 +137,7 @@ impl ChunkManager { Some(*chunk_position) } - None => { - // FIXME: we should do something about updates in unloaded chunks.. - None - } + None => None, } }) .collect() @@ -143,10 +146,11 @@ impl ChunkManager { pub fn get_block(&self, position: IVec3) -> Option { match self.chunk_at_position(position) { Some(chunk) => { + // TODO: inline into ChunkPosition let chunk_position = IVec3::new( - chunk.position[0] * CHUNK_SIZE as i32, + chunk.position.x * CHUNK_SIZE as i32, 0, - chunk.position[1] * CHUNK_SIZE as i32, + chunk.position.z * CHUNK_SIZE as i32, ); let local_position = position - chunk_position; chunk.get_safe(local_position.x, local_position.y, local_position.z) @@ -158,7 +162,7 @@ impl ChunkManager { } } - fn chunk_positions_containing_world_pos(position: IVec3) -> Vec { + fn chunk_positions_containing_world_pos(position: IVec3) -> Vec { fn axis_chunks(world: i32) -> Vec { let size = CHUNK_SIZE as i32; let base = world.div_euclid(size); @@ -182,15 +186,15 @@ impl ChunkManager { for x in xs { for z in &zs { - out.push(IVec2::new(x, *z)); + out.push(ChunkPosition::new(x, *z)); } } out } - pub fn world_position_to_chunk_position(world_position: IVec3) -> IVec2 { - IVec2::new( + pub fn world_position_to_chunk_position(world_position: IVec3) -> ChunkPosition { + ChunkPosition::new( world_position.x.div_euclid(CHUNK_SIZE as i32), world_position.z.div_euclid(CHUNK_SIZE as i32), ) @@ -201,7 +205,7 @@ impl ChunkManager { self.get_chunk(&chunk_position) } - pub fn get_all_chunk_positions(&self) -> Vec { + pub fn get_all_chunk_positions(&self) -> Vec { self.chunks.keys().copied().collect() } @@ -231,26 +235,26 @@ mod tests { assert_eq!( ChunkManager::chunk_positions_containing_world_pos(IVec3::ZERO), vec![ - IVec2::new(-1, -1), - IVec2::new(-1, 0), - IVec2::new(0, -1), - IVec2::new(0, 0) + ChunkPosition::new(-1, -1), + ChunkPosition::new(-1, 0), + ChunkPosition::new(0, -1), + ChunkPosition::new(0, 0) ] ); assert_eq!( ChunkManager::chunk_positions_containing_world_pos(IVec3::ONE), - vec![IVec2::new(0, 0),] + vec![ChunkPosition::new(0, 0),] ); assert_eq!( ChunkManager::chunk_positions_containing_world_pos(IVec3::new(0, 1, 1)), - vec![IVec2::new(-1, 0), IVec2::new(0, 0),] + vec![ChunkPosition::new(-1, 0), ChunkPosition::new(0, 0),] ); assert_eq!( ChunkManager::chunk_positions_containing_world_pos(IVec3::new(CHUNK_SIZE as i32, 1, 1)), - vec![IVec2::new(0, 0), IVec2::new(1, 0),] + vec![ChunkPosition::new(0, 0), ChunkPosition::new(1, 0),] ); } @@ -262,7 +266,7 @@ mod tests { #[test] fn test_instantiate_chunks() { - let position = IVec2::new(0, 0); + let position = ChunkPosition::ZERO; let width = 2; let depth = 4; @@ -276,7 +280,7 @@ mod tests { #[test] fn test_insert_chunks() { let mut chunk_manager = ChunkManager::new(); - let position = IVec2::new(0, 0); + let position = ChunkPosition::ZERO; let render_distance = 2; let chunks = ChunkManager::instantiate_chunks( position, @@ -295,7 +299,7 @@ mod tests { #[test] fn test_set_and_get_chunk_mut() { let mut chunk_manager = ChunkManager::new(); - let position = IVec2::ZERO; + let position = ChunkPosition::ZERO; let chunk = Chunk::new(position); chunk_manager.set_chunk(position, chunk.clone()); @@ -306,7 +310,7 @@ mod tests { #[test] fn test_set_and_get_block() { let mut chunk_manager = ChunkManager::new(); - let position = IVec2::ZERO; + let position = ChunkPosition::ZERO; let chunk = Chunk::new(position); chunk_manager.set_chunk(position, chunk); @@ -321,9 +325,9 @@ mod tests { #[test] fn test_get_all_chunk_positions() { let mut chunk_manager = ChunkManager::new(); - chunk_manager.set_chunk(IVec2::new(0, 0), Chunk::default()); - chunk_manager.set_chunk(IVec2::new(2, 0), Chunk::default()); - chunk_manager.set_chunk(IVec2::new(1, 3), Chunk::default()); + chunk_manager.set_chunk(ChunkPosition::new(0, 0), Chunk::default()); + chunk_manager.set_chunk(ChunkPosition::new(2, 0), Chunk::default()); + chunk_manager.set_chunk(ChunkPosition::new(1, 3), Chunk::default()); let retrieved_chunk_positions = chunk_manager.get_all_chunk_positions(); assert_eq!(retrieved_chunk_positions.len(), 3); @@ -333,7 +337,7 @@ mod tests { #[rustfmt::skip] fn test_tallgrass_update() { let mut chunk_manager = ChunkManager::new(); - let chunk_position = IVec2::ZERO; + let chunk_position = ChunkPosition::ZERO; let chunk = Chunk::new(chunk_position); chunk_manager.set_chunk(chunk_position, chunk); diff --git a/src/shared/chunk_serializer.rs b/src/shared/chunk_serializer.rs index 8d07cc9..2866771 100644 --- a/src/shared/chunk_serializer.rs +++ b/src/shared/chunk_serializer.rs @@ -2,8 +2,8 @@ use crate::deserialize_buffer; use crate::serialize_buffer; use crate::BlockId; use crate::Chunk; +use crate::ChunkPosition; use crate::CHUNK_LENGTH; -use bevy::math::IVec2; use serde::ser::SerializeStruct; use serde::{Deserialize, Serialize}; @@ -48,7 +48,7 @@ impl<'de> Deserialize<'de> for Chunk { #[derive(Deserialize)] struct ChunkData { data: BytesVec, - position: IVec2, + position: ChunkPosition, } let ChunkData { data, position } = ChunkData::deserialize(deserializer)?; diff --git a/src/shared/networking.rs b/src/shared/networking.rs index 928162d..689434e 100644 --- a/src/shared/networking.rs +++ b/src/shared/networking.rs @@ -5,7 +5,7 @@ use std::{ use bevy::{ ecs::resource::Resource, - math::{IVec2, IVec3, Quat, Vec3}, + math::{IVec3, Quat, Vec3}, }; use bevy_renet::netcode::NETCODE_USER_DATA_BYTES; use chrono::DateTime; @@ -13,7 +13,7 @@ use renet::{ChannelConfig, ClientId, ConnectionConfig, SendType}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; -use super::{BlockId, Chunk}; +use super::{BlockId, Chunk, ChunkPosition}; pub const SERVER_USERNAME: &str = "SERVER"; pub const MAX_USERNAME_LENGTH_BYTES: usize = 50; @@ -211,7 +211,7 @@ pub enum NetworkingMessage { PlayerLeave(Username), PlayerUpdate(PlayerState), PlayerSync(HashMap), - ChunkBatchRequest(Vec), + ChunkBatchRequest(Vec), ChunkBatchResponse(Vec), ChatMessageSend(String), SingleChatMessageSync(ChatMessage), From 1c54540b552a8d6c9c4508c642ec7502cb4d114b Mon Sep 17 00:00:00 2001 From: Daniel Bengl Date: Sun, 15 Feb 2026 12:56:15 +0100 Subject: [PATCH 10/15] Remove dead code --- src/client/player/resources.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/client/player/resources.rs b/src/client/player/resources.rs index b205b41..a766713 100644 --- a/src/client/player/resources.rs +++ b/src/client/player/resources.rs @@ -48,10 +48,6 @@ impl LastPlayerPosition { Self(IVec3::ZERO) } - pub fn chunk_position(&self) -> ChunkPosition { - Self::chunk_pos(self.0) - } - pub fn has_same_chunk_position_as(&self, other_world_position: IVec3) -> bool { Self::chunk_pos(self.0) == Self::chunk_pos(other_world_position) } From e5fc788bfa9f78c95524a5dab14e94257f4936a3 Mon Sep 17 00:00:00 2001 From: Daniel Bengl Date: Sun, 15 Feb 2026 13:08:12 +0100 Subject: [PATCH 11/15] Impl as_ivec3 --- src/server/terrain/util/generator.rs | 3 +-- src/shared/chunk/chunk_data.rs | 4 ++++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/server/terrain/util/generator.rs b/src/server/terrain/util/generator.rs index 0decf8b..4a325e9 100644 --- a/src/server/terrain/util/generator.rs +++ b/src/server/terrain/util/generator.rs @@ -20,8 +20,7 @@ macro_rules! for_each_chunk_coordinate { let chunk_origin = $chunk.position * CHUNK_SIZE as i32; let local_position = IVec3::new(x as i32, y as i32, z as i32); - let world_position = - IVec3::new(chunk_origin[0], 0, chunk_origin[1]) + local_position; + let world_position = chunk_origin.as_ivec3() + local_position; $body(x, y, z, world_position); } diff --git a/src/shared/chunk/chunk_data.rs b/src/shared/chunk/chunk_data.rs index 0c51dde..db431d0 100644 --- a/src/shared/chunk/chunk_data.rs +++ b/src/shared/chunk/chunk_data.rs @@ -50,6 +50,10 @@ impl ChunkPosition { pub fn as_vec2(self) -> bevy::math::Vec2 { bevy::math::Vec2::new(self.x as f32, self.z as f32) } + + pub fn as_ivec3(self) -> IVec3 { + IVec3::new(self.x, 0, self.z) + } } impl std::ops::Index for ChunkPosition { From 8916a02541af105421c69fd106f89f3575f07b56 Mon Sep 17 00:00:00 2001 From: Daniel Bengl Date: Sun, 15 Feb 2026 13:09:57 +0100 Subject: [PATCH 12/15] Refactor --- src/client/terrain/systems.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/client/terrain/systems.rs b/src/client/terrain/systems.rs index 185897b..48ff363 100644 --- a/src/client/terrain/systems.rs +++ b/src/client/terrain/systems.rs @@ -258,9 +258,9 @@ fn create_chunk_bundle( ( Mesh3d(mesh_handle), Transform::from_xyz( - chunk_position[0] * CHUNK_SIZE as f32, + chunk_position.x * CHUNK_SIZE as f32, 0.0, - chunk_position[1] * CHUNK_SIZE as f32, + chunk_position.z * CHUNK_SIZE as f32, ), terrain_components::ChunkMesh { key: [chunk_position[0] as i32, chunk_position[1] as i32], From 1839c76b31b87c6693df1b5ef632f5bccc3cc879 Mon Sep 17 00:00:00 2001 From: Daniel Bengl Date: Sun, 15 Feb 2026 13:11:54 +0100 Subject: [PATCH 13/15] Refactor --- src/client/terrain/systems.rs | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/client/terrain/systems.rs b/src/client/terrain/systems.rs index 48ff363..3f8a2fd 100644 --- a/src/client/terrain/systems.rs +++ b/src/client/terrain/systems.rs @@ -165,10 +165,9 @@ pub fn handle_chunk_tasks_system( }; completed += 1; - let pos = future_chunk.position; - let pos_vec = pos.as_vec2(); + let chunk_position = future_chunk.position; - if let Some(entities) = chunk_entities.remove(pos) { + if let Some(entities) = chunk_entities.remove(chunk_position) { entities.iter().for_each(|entity| { commands.entity(*entity).despawn(); }) @@ -178,25 +177,25 @@ pub fn handle_chunk_tasks_system( let entity = commands .spawn(create_chunk_bundle( meshes.add(mesh), - pos_vec, + chunk_position, MeshType::Transparent, materials.transparent_material.clone().unwrap(), )) .id(); - chunk_entities.add(pos, entity); + chunk_entities.add(chunk_position, entity); } if let Some(mesh) = mesh_option.cube_mesh { let entity = commands .spawn(create_chunk_bundle( meshes.add(mesh), - pos_vec, + chunk_position, MeshType::Solid, materials.chunk_material.clone().unwrap(), )) .insert(player_components::Raycastable) .id(); - chunk_entities.add(pos, entity); + chunk_entities.add(chunk_position, entity); } DISCARD @@ -246,7 +245,7 @@ pub fn check_if_spawn_area_is_loaded_system( fn create_chunk_bundle( mesh_handle: Handle, - chunk_position: Vec2, + chunk_position: ChunkPosition, mesh_type: MeshType, material_handle: Handle, ) -> ( @@ -258,12 +257,12 @@ fn create_chunk_bundle( ( Mesh3d(mesh_handle), Transform::from_xyz( - chunk_position.x * CHUNK_SIZE as f32, + chunk_position.x as f32 * CHUNK_SIZE as f32, 0.0, - chunk_position.z * CHUNK_SIZE as f32, + chunk_position.z as f32 * CHUNK_SIZE as f32, ), terrain_components::ChunkMesh { - key: [chunk_position[0] as i32, chunk_position[1] as i32], + key: [chunk_position.x, chunk_position.z], mesh_type, }, MeshMaterial3d(material_handle), From 5a4dd8f84792ab2b9881035850041812da1d8be7 Mon Sep 17 00:00:00 2001 From: Daniel Bengl Date: Sun, 15 Feb 2026 13:23:22 +0100 Subject: [PATCH 14/15] Remove lenght_squared --- src/shared/chunk/chunk_data.rs | 4 ---- src/shared/chunk/manager.rs | 7 ++++--- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/shared/chunk/chunk_data.rs b/src/shared/chunk/chunk_data.rs index db431d0..758918d 100644 --- a/src/shared/chunk/chunk_data.rs +++ b/src/shared/chunk/chunk_data.rs @@ -43,10 +43,6 @@ impl ChunkPosition { Self { x, z } } - pub fn length_squared(self) -> i32 { - self.x * self.x + self.z * self.z - } - pub fn as_vec2(self) -> bevy::math::Vec2 { bevy::math::Vec2::new(self.x as f32, self.z as f32) } diff --git a/src/shared/chunk/manager.rs b/src/shared/chunk/manager.rs index 8a9e701..a3e8bb5 100644 --- a/src/shared/chunk/manager.rs +++ b/src/shared/chunk/manager.rs @@ -78,9 +78,10 @@ impl ChunkManager { } positions.sort_by(|a, b| { - (*a - origin) - .length_squared() - .cmp(&(*b - origin).length_squared()) + let a = (*a - origin).as_ivec3(); + let b = (*b - origin).as_ivec3(); + + a.length_squared().cmp(&b.length_squared()) }); positions From 3a94ae821047bc8417abedb9e6e474a9cfcfd298 Mon Sep 17 00:00:00 2001 From: Daniel Bengl Date: Sun, 15 Feb 2026 13:28:42 +0100 Subject: [PATCH 15/15] Add to_world_pos --- src/shared/chunk/chunk_data.rs | 4 ++++ src/shared/chunk/manager.rs | 8 +------- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/shared/chunk/chunk_data.rs b/src/shared/chunk/chunk_data.rs index 758918d..d45b377 100644 --- a/src/shared/chunk/chunk_data.rs +++ b/src/shared/chunk/chunk_data.rs @@ -50,6 +50,10 @@ impl ChunkPosition { pub fn as_ivec3(self) -> IVec3 { IVec3::new(self.x, 0, self.z) } + + pub fn to_world_position(self) -> IVec3 { + IVec3::new(self.x * CHUNK_SIZE as i32, 0, self.z * CHUNK_SIZE as i32) + } } impl std::ops::Index for ChunkPosition { diff --git a/src/shared/chunk/manager.rs b/src/shared/chunk/manager.rs index a3e8bb5..f4c6e07 100644 --- a/src/shared/chunk/manager.rs +++ b/src/shared/chunk/manager.rs @@ -147,13 +147,7 @@ impl ChunkManager { pub fn get_block(&self, position: IVec3) -> Option { match self.chunk_at_position(position) { Some(chunk) => { - // TODO: inline into ChunkPosition - let chunk_position = IVec3::new( - chunk.position.x * CHUNK_SIZE as i32, - 0, - chunk.position.z * CHUNK_SIZE as i32, - ); - let local_position = position - chunk_position; + let local_position = position - chunk.position.to_world_position(); chunk.get_safe(local_position.x, local_position.y, local_position.z) } None => {