From d34916ceef663bb9239659057401ecdabf517bf7 Mon Sep 17 00:00:00 2001 From: DimitriTimoz Date: Wed, 8 Nov 2023 21:06:23 +0100 Subject: [PATCH 01/46] Add light network packet --- minecraft-protocol/src/components/light.rs | 0 minecraft-protocol/src/components/mod.rs | 1 + .../src/packets/play_clientbound.rs | 16 ++++++++++++++-- 3 files changed, 15 insertions(+), 2 deletions(-) create mode 100644 minecraft-protocol/src/components/light.rs diff --git a/minecraft-protocol/src/components/light.rs b/minecraft-protocol/src/components/light.rs new file mode 100644 index 00000000..e69de29b diff --git a/minecraft-protocol/src/components/mod.rs b/minecraft-protocol/src/components/mod.rs index 05fe9874..0bfa5b2e 100644 --- a/minecraft-protocol/src/components/mod.rs +++ b/minecraft-protocol/src/components/mod.rs @@ -22,3 +22,4 @@ pub mod sound; pub mod tags; pub mod teams; pub mod trades; +pub mod light; \ No newline at end of file diff --git a/minecraft-protocol/src/packets/play_clientbound.rs b/minecraft-protocol/src/packets/play_clientbound.rs index c39d2c1d..0af87f5e 100644 --- a/minecraft-protocol/src/packets/play_clientbound.rs +++ b/minecraft-protocol/src/packets/play_clientbound.rs @@ -443,8 +443,20 @@ pub enum ClientboundPacket<'a> { /// Updates light levels for a chunk UpdateLight { - /// TODO: parse this - data: RawBytes<'a>, + /// Chunk coordinate (block coordinate divided by 16, rounded down) + cx: VarInt, + /// Chunk coordinate (block coordinate divided by 16, rounded down) + cz: VarInt, + /// BitSet containing bits for each section in the world + 2. Each set bit indicates that the corresponding 16×16×16 chunk section has data in the Sky Light array below. The least significant bit is for blocks 16 blocks to 1 block below the min world height (one section below the world), while the most significant bit covers blocks 1 to 16 blocks above the max world height (one section above the world). + sky_light_mask: BitSet<'a>, + /// BitSet containing bits for each section in the world + 2. Each set bit indicates that the corresponding 16×16×16 chunk section has data in the Block Light array below. The order of bits is the same as in Sky Light Mask. + block_light_mask: BitSet<'a>, + /// BitSet containing bits for each section in the world + 2. Each set bit indicates that the corresponding 16×16×16 chunk section has all zeros for its Sky Light data. The order of bits is the same as in Sky Light Mask. + empty_sky_light_mask: BitSet<'a>, + /// BitSet containing bits for each section in the world + 2. Each set bit indicates that the corresponding 16×16×16 chunk section has all zeros for its Block Light data. The order of bits is the same as in Sky Light Mask. + empty_block_light_mask: BitSet<'a>, + sky_light_arrays: Array<'a, Array<'a, u8, VarInt>, VarInt>, + block_light_arrays: Array<'a, Array<'a, u8, VarInt>, VarInt>, }, /// See [Protocol Encryption](https://wiki.vg/Protocol_Encryption) for information on logging in. From f2e9c2e8948ca192674c3e7474e7b8f7f710b43c Mon Sep 17 00:00:00 2001 From: DimitriTimoz Date: Wed, 8 Nov 2023 21:20:47 +0100 Subject: [PATCH 02/46] Array to BitSet ChunkData --- minecraft-protocol/src/components/chunk.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/minecraft-protocol/src/components/chunk.rs b/minecraft-protocol/src/components/chunk.rs index 843e9d76..97f1b8d8 100644 --- a/minecraft-protocol/src/components/chunk.rs +++ b/minecraft-protocol/src/components/chunk.rs @@ -1,4 +1,4 @@ -use crate::{nbt::NbtTag, *, components::blocks::BlockEntity}; +use crate::{nbt::NbtTag, *, components::blocks::BlockEntity, packets::serializer::BitSet}; /// A complex data structure including block data and optionally entities of a chunk. /// @@ -24,19 +24,19 @@ pub struct ChunkData<'a> { /// BitSet containing bits for each section in the world + 2. /// Each set bit indicates that the corresponding 16×16×16 chunk section has data in the Sky Light array below. /// The least significant bit is for blocks 16 blocks to 1 block below the min world height (one section below the world), while the most significant bit covers blocks 1 to 16 blocks above the max world height (one section above the world). - pub sky_light_mask: Array<'a, u64, VarInt>, + pub sky_light_mask: BitSet<'a>, /// BitSet containing bits for each section in the world + 2. /// Each set bit indicates that the corresponding 16×16×16 chunk section has data in the Block Light array below. /// The order of bits is the same as in Sky Light Mask. - pub block_light_mask: Array<'a, u64, VarInt>, + pub block_light_mask: BitSet<'a>, /// BitSet containing bits for each section in the world + 2. /// Each set bit indicates that the corresponding 16×16×16 chunk section has data in the Block Light array below. /// The order of bits is the same as in Sky Light Mask. - pub empty_sky_light_mask: Array<'a, u64, VarInt>, + pub empty_sky_light_mask: BitSet<'a>, /// BitSet containing bits for each section in the world + 2. /// Each set bit indicates that the corresponding 16×16×16 chunk section has data in the Block Light array below. /// The order of bits is the same as in Sky Light Mask. - pub empty_block_light_mask: Array<'a, u64, VarInt>, + pub empty_block_light_mask: BitSet<'a>, /// Length should match the number of bits set in Sky Light Mask. /// Each entry is an array of 2048 bytes. /// There is 1 array for each bit set to true in the sky light mask, starting with the lowest value. Half a byte per light value. Indexed ((y<<8) | (z<<4) | x) / 2 From 657e9f09ca16806a22c66d55e96a59733dd0a006 Mon Sep 17 00:00:00 2001 From: DimitriTimoz Date: Wed, 8 Nov 2023 23:28:15 +0100 Subject: [PATCH 03/46] Setter HeightMap and Getter WIP --- minecraft-protocol/src/components/light.rs | 0 minecraft-protocol/src/components/mod.rs | 3 +- minecraft-server/src/map.rs | 62 +++++++++++++++++++++- 3 files changed, 61 insertions(+), 4 deletions(-) delete mode 100644 minecraft-protocol/src/components/light.rs diff --git a/minecraft-protocol/src/components/light.rs b/minecraft-protocol/src/components/light.rs deleted file mode 100644 index e69de29b..00000000 diff --git a/minecraft-protocol/src/components/mod.rs b/minecraft-protocol/src/components/mod.rs index 0bfa5b2e..abea70e2 100644 --- a/minecraft-protocol/src/components/mod.rs +++ b/minecraft-protocol/src/components/mod.rs @@ -21,5 +21,4 @@ pub mod slots; pub mod sound; pub mod tags; pub mod teams; -pub mod trades; -pub mod light; \ No newline at end of file +pub mod trades; \ No newline at end of file diff --git a/minecraft-server/src/map.rs b/minecraft-server/src/map.rs index a24c8d61..0e291ac9 100644 --- a/minecraft-server/src/map.rs +++ b/minecraft-server/src/map.rs @@ -1,4 +1,4 @@ -use std::collections::HashMap; +use std::{collections::HashMap, mem::transmute}; use minecraft_protocol::components::chunk::PalettedData; use tokio::sync::RwLock; use crate::prelude::*; @@ -173,7 +173,57 @@ impl Chunk { } } +struct HeightMap { + entry_bit_size: u8, + data: Vec, +} + +impl HeightMap { + pub fn from(entry_bit_size: u8) -> Self { + assert!(entry_bit_size <= 9); + Self { + entry_bit_size, + data: vec![0; ((16 * 16 * 9usize).div_euclid(entry_bit_size as usize) + 1) * entry_bit_size as usize ], + } + } + pub(self) fn get(&self, position: BlockPositionInChunkColumn) -> i32 { + let bits_position = (position.bz as usize * 16 + position.bx as usize) * self.entry_bit_size as usize; + let mut data = self.data[bits_position / 64]; + let bits_position = bits_position % 64; + data = data.rotate_right(bits_position as u32); + if bits_position + self.entry_bit_size as usize > 64 { + let mut data2 = self.data[bits_position / 64 + 1]; + data2 = data2.rotate_left(64 - bits_position as u32); + data |= data2; + data &= (1 << self.entry_bit_size) - 1; + } + + unsafe { + transmute::(data) as i32 + } + } + + fn set(&mut self, position: BlockPositionInChunkColumn, height: i32) { + let bits_position = (position.bz as usize * 16 + position.bx as usize) * self.entry_bit_size as usize; + let mut data = unsafe { + transmute::(height as i64) + }; + + let bits_position = bits_position % 64; + if bits_position + self.entry_bit_size as usize > 64 { + let mut data2 = self.data[bits_position / 64 + 1]; + data2 = data2.rotate_left(64 - bits_position as u32); + } + + data = data.rotate_left(bits_position as u32); + self.data[bits_position / 64] = data; + + } +} + + struct ChunkColumn { + heightmap: HeightMap, chunks: Vec, } @@ -200,7 +250,10 @@ impl ChunkColumn { for _ in 0..23 { chunks.push(empty_chunk.clone()); } - ChunkColumn { chunks } + Self { + chunks, + heightmap: HeightMap::from(1), + } } fn get_block(&self, position: BlockPositionInChunkColumn) -> BlockWithState { @@ -437,4 +490,9 @@ mod tests { } } } + + #[test] + fn test_heightmap_get_and_set() { + + } } From a14c53f048814b1d15ebb578867992a65bc0645a Mon Sep 17 00:00:00 2001 From: DimitriTimoz Date: Fri, 10 Nov 2023 01:28:37 +0100 Subject: [PATCH 04/46] The heightMap --- minecraft-server/src/map.rs | 116 +++++++++++++++++++++++++++--------- 1 file changed, 89 insertions(+), 27 deletions(-) diff --git a/minecraft-server/src/map.rs b/minecraft-server/src/map.rs index 0e291ac9..1dc99369 100644 --- a/minecraft-server/src/map.rs +++ b/minecraft-server/src/map.rs @@ -186,39 +186,85 @@ impl HeightMap { data: vec![0; ((16 * 16 * 9usize).div_euclid(entry_bit_size as usize) + 1) * entry_bit_size as usize ], } } - pub(self) fn get(&self, position: BlockPositionInChunkColumn) -> i32 { - let bits_position = (position.bz as usize * 16 + position.bx as usize) * self.entry_bit_size as usize; - let mut data = self.data[bits_position / 64]; - let bits_position = bits_position % 64; - data = data.rotate_right(bits_position as u32); - if bits_position + self.entry_bit_size as usize > 64 { - let mut data2 = self.data[bits_position / 64 + 1]; - data2 = data2.rotate_left(64 - bits_position as u32); - data |= data2; - data &= (1 << self.entry_bit_size) - 1; - } - - unsafe { - transmute::(data) as i32 + + /// Set the height of the highest block at the given position. + pub fn set(&mut self, position: BlockPositionInChunkColumn) { + let (x, z) = (position.bx, position.bz); + let height = position.y; + + let index = (x * 16 + z) as usize; // assuming a 16x16 chunk column + let bits_per_entry = self.entry_bit_size as usize; + let bit_pos = index * bits_per_entry; + let data_index = bit_pos / 64; + let bit_offset = bit_pos % 64; + + // Ensure we don't shift beyond the limits of the data type. + if bits_per_entry >= 64 { + panic!("entry_bit_size too large for u64 storage"); } - } - fn set(&mut self, position: BlockPositionInChunkColumn, height: i32) { - let bits_position = (position.bz as usize * 16 + position.bx as usize) * self.entry_bit_size as usize; - let mut data = unsafe { - transmute::(height as i64) + // Calculate the signed height ensuring it doesn't overflow. + let signed_height = if bits_per_entry < 64 { + ((height as u64) << (64 - bits_per_entry)) >> (64 - bits_per_entry) + } else { + height as u64 }; - - let bits_position = bits_position % 64; - if bits_position + self.entry_bit_size as usize > 64 { - let mut data2 = self.data[bits_position / 64 + 1]; - data2 = data2.rotate_left(64 - bits_position as u32); + + // Prepare the mask to clear the bits at the position. + let mask = ((1 << bits_per_entry) - 1) << bit_offset; + // Clear the bits at the target position. + self.data[data_index] &= !mask; + // Set the new height with the sign. + self.data[data_index] |= signed_height << bit_offset; + // Check if the entry spills over to the next u64. + if bit_offset + bits_per_entry > 64 { + // Calculate how many bits spill over. + let spill_over_bits = bit_offset + bits_per_entry - 64; + // Prepare the mask to clear the spill over bits. + let spill_over_mask = (1 << spill_over_bits) - 1; + // Clear the spill over bits in the next entry. + self.data[data_index + 1] &= !spill_over_mask; + // Set the spill over bits. + self.data[data_index + 1] |= signed_height >> (64 - bit_offset); + } + } + + /// Get the height of the highest block at the given position. + pub fn get(&self, position: BlockPositionInChunkColumn) -> i32 { + let (x, z) = (position.bx, position.bz); + + let index = (x * 16 + z) as usize; // assuming a 16x16 chunk column + let bits_per_entry = self.entry_bit_size as usize; + let bit_pos = index * bits_per_entry; + let data_index = bit_pos / 64; + let bit_offset = bit_pos % 64; + + // Prepare the mask to get the bits at the position. + let mask = ((1u64 << bits_per_entry) - 1) << bit_offset; + // Retrieve the bits. + let mut value = (self.data[data_index] & mask) >> bit_offset; + + // Check if the entry spills over to the next u64 and retrieve the remaining bits. + if bit_offset + bits_per_entry > 64 { + // Calculate how many bits spill over. + let spill_over_bits = bit_offset + bits_per_entry - 64; + // Prepare the mask to get the spill over bits. + let spill_over_mask = (1u64 << spill_over_bits) - 1; + // Retrieve the spill over bits from the next entry. + value |= (self.data[data_index + 1] & spill_over_mask) << (64 - bit_offset); } - data = data.rotate_left(bits_position as u32); - self.data[bits_position / 64] = data; + // Perform sign extension if the value is negative. + let sign_bit = 1u64 << (bits_per_entry - 1); + if value & sign_bit != 0 { + // If the sign bit is set, extend the sign to the rest of the i64. + value |= !((1u64 << bits_per_entry) - 1); + } + // Cast to i32 with sign extension. + value as i32 } + } @@ -493,6 +539,22 @@ mod tests { #[test] fn test_heightmap_get_and_set() { - + let mut heightmap = HeightMap::from(9); + heightmap.set(BlockPositionInChunkColumn { bx: 0, y: 0, bz: 0 }); + heightmap.set(BlockPositionInChunkColumn { bx: 0, y: -2, bz: 1 }); + heightmap.set(BlockPositionInChunkColumn { bx: 0, y: 3, bz: 2 }); + heightmap.set(BlockPositionInChunkColumn { bx: 0, y: -4, bz: 3 }); + heightmap.set(BlockPositionInChunkColumn { bx: 0, y: -4, bz: 7 }); + + // Test get + assert_eq!(heightmap.get(BlockPositionInChunkColumn { bx: 0, y: 0, bz: 0 }), 0); + assert_eq!(heightmap.get(BlockPositionInChunkColumn { bx: 0, y: 0, bz: 1 }), -2); + assert_eq!(heightmap.get(BlockPositionInChunkColumn { bx: 0, y: 0, bz: 2 }), 3); + assert_eq!(heightmap.get(BlockPositionInChunkColumn { bx: 0, y: 0, bz: 3 }), -4); + assert_eq!(heightmap.get(BlockPositionInChunkColumn { bx: 0, y: 0, bz: 7 }), -4); + + // Test erase + heightmap.set(BlockPositionInChunkColumn { bx: 0, y: 12, bz: 0 }); + assert_eq!(heightmap.get(BlockPositionInChunkColumn { bx: 0, y: 12, bz: 0 }), 12); } } From 07c1fbf8f347c3f7c8edcadfb25ff9c6760231ed Mon Sep 17 00:00:00 2001 From: Dimitri Date: Fri, 10 Nov 2023 13:09:41 +0100 Subject: [PATCH 05/46] Fix somethings --- minecraft-server/src/map.rs | 44 +++++++++++++++++++++++++++---------- 1 file changed, 33 insertions(+), 11 deletions(-) diff --git a/minecraft-server/src/map.rs b/minecraft-server/src/map.rs index 1dc99369..7d1bb2d2 100644 --- a/minecraft-server/src/map.rs +++ b/minecraft-server/src/map.rs @@ -1,4 +1,4 @@ -use std::{collections::HashMap, mem::transmute}; +use std::collections::HashMap; use minecraft_protocol::components::chunk::PalettedData; use tokio::sync::RwLock; use crate::prelude::*; @@ -174,33 +174,55 @@ impl Chunk { } struct HeightMap { - entry_bit_size: u8, + base: u8, data: Vec, + max_height: Option, } impl HeightMap { - pub fn from(entry_bit_size: u8) -> Self { - assert!(entry_bit_size <= 9); + pub fn new(base: u8) -> Self { + assert!(base <= 9); Self { - entry_bit_size, - data: vec![0; ((16 * 16 * 9usize).div_euclid(entry_bit_size as usize) + 1) * entry_bit_size as usize ], + base, + data: vec![0; ((16 * 16 * 9usize).div_euclid(base as usize) + 1) * base as usize ], + max_height: None } } + + /// Update the current base of the heightmap. + fn new_base(&mut self, base: u8) { + + } + fn get_need_base(&self, height: i32) -> u8 { + 32 - ((height + 1).leading_zeros() as u8) + } /// Set the height of the highest block at the given position. pub fn set(&mut self, position: BlockPositionInChunkColumn) { let (x, z) = (position.bx, position.bz); let height = position.y; + // Check if the height is higher than the current max height. + if let Some(max_height) = self.max_height { + if height < max_height { // Calculate the new base for the data. + let new_base = self.get_need_base(height); + // Update the base & max height. + self.max_height = Some(height); + } + } else { + // Set the max height. + self.max_height = Some(height); + } + let index = (x * 16 + z) as usize; // assuming a 16x16 chunk column - let bits_per_entry = self.entry_bit_size as usize; + let bits_per_entry = self.base as usize; let bit_pos = index * bits_per_entry; let data_index = bit_pos / 64; let bit_offset = bit_pos % 64; // Ensure we don't shift beyond the limits of the data type. if bits_per_entry >= 64 { - panic!("entry_bit_size too large for u64 storage"); + panic!("base too large for u64 storage"); } // Calculate the signed height ensuring it doesn't overflow. @@ -234,7 +256,7 @@ impl HeightMap { let (x, z) = (position.bx, position.bz); let index = (x * 16 + z) as usize; // assuming a 16x16 chunk column - let bits_per_entry = self.entry_bit_size as usize; + let bits_per_entry = self.base as usize; let bit_pos = index * bits_per_entry; let data_index = bit_pos / 64; let bit_offset = bit_pos % 64; @@ -298,7 +320,7 @@ impl ChunkColumn { } Self { chunks, - heightmap: HeightMap::from(1), + heightmap: HeightMap::new(1), } } @@ -539,7 +561,7 @@ mod tests { #[test] fn test_heightmap_get_and_set() { - let mut heightmap = HeightMap::from(9); + let mut heightmap = HeightMap::new(9); heightmap.set(BlockPositionInChunkColumn { bx: 0, y: 0, bz: 0 }); heightmap.set(BlockPositionInChunkColumn { bx: 0, y: -2, bz: 1 }); heightmap.set(BlockPositionInChunkColumn { bx: 0, y: 3, bz: 2 }); From addb975756e6eb3927f52baeb2e5d01cb14757e8 Mon Sep 17 00:00:00 2001 From: Dimitri Date: Fri, 10 Nov 2023 16:48:52 +0100 Subject: [PATCH 06/46] Add heightmap to ChunkColumn (must be tested) --- minecraft-server/src/map.rs | 50 +++++++++++++++++++++++++++++++++---- 1 file changed, 45 insertions(+), 5 deletions(-) diff --git a/minecraft-server/src/map.rs b/minecraft-server/src/map.rs index 7d1bb2d2..191cf344 100644 --- a/minecraft-server/src/map.rs +++ b/minecraft-server/src/map.rs @@ -1,5 +1,5 @@ use std::collections::HashMap; -use minecraft_protocol::components::chunk::PalettedData; +use minecraft_protocol::{components::chunk::PalettedData, ids::blocks::Block}; use tokio::sync::RwLock; use crate::prelude::*; @@ -296,6 +296,43 @@ struct ChunkColumn { } impl ChunkColumn { + const MAX_HEIGHT: i32 = 320; // TODO: adapt to the world height + + fn init_chunk_heightmap(&mut self){ + self.heightmap = HeightMap::new(9); + if self.chunks.len() != 24 { + panic!("Chunk column must have 24 chunks"); + } + + // Start from the higher chunk + for bx in 0..16 { + for bz in 0..16 { + let mut current_height = Self::MAX_HEIGHT; + 'chunks: for chunk in self.chunks.iter() { + while current_height >= 0 { + let block: BlockWithState = chunk.get_block(BlockPositionInChunk { bx, by: (current_height % 16) as u8, bz }); + // SAFETY: fom_id will get a valid block necessarily + if Block::from_id(block.block_id()).unwrap().is_air_block() { + break 'chunks; + } + current_height -= 1; + + if (current_height % 16) <= 0 { + break 'chunks; + } + } + } + self.heightmap.set(BlockPositionInChunkColumn { bx, y: current_height, bz }); + } + } + } + + pub fn from(chunks: Vec) -> Self { + let mut column = Self { chunks, heightmap: HeightMap::new(9) }; + column.init_chunk_heightmap(); + column + } + pub fn flat() -> Self { let empty_chunk = Chunk { data: NetworkChunk { @@ -318,10 +355,7 @@ impl ChunkColumn { for _ in 0..23 { chunks.push(empty_chunk.clone()); } - Self { - chunks, - heightmap: HeightMap::new(1), - } + Self::from(chunks) } fn get_block(&self, position: BlockPositionInChunkColumn) -> BlockWithState { @@ -519,6 +553,10 @@ mod tests { let high_block = flat_column.get_block(BlockPositionInChunkColumn { bx: 0, y: 120, bz: 0 }); assert_eq!(high_block.block_state_id().unwrap(), BlockWithState::Air.block_state_id().unwrap()); + + // Check that the heightmap is correct + let heightmap = &flat_column.heightmap; + assert_eq!(heightmap.get(BlockPositionInChunkColumn { bx: 0, y: 0, bz: 0 }), 120); } #[tokio::test] @@ -579,4 +617,6 @@ mod tests { heightmap.set(BlockPositionInChunkColumn { bx: 0, y: 12, bz: 0 }); assert_eq!(heightmap.get(BlockPositionInChunkColumn { bx: 0, y: 12, bz: 0 }), 12); } + + } From 6e43296982ba60d2ad1963c707a9cbabacb62af9 Mon Sep 17 00:00:00 2001 From: DimitriTimoz Date: Sat, 11 Nov 2023 02:00:01 +0100 Subject: [PATCH 07/46] Use the height and not the coordinate --- minecraft-server/src/map.rs | 67 +++++++++++++++++-------------------- 1 file changed, 30 insertions(+), 37 deletions(-) diff --git a/minecraft-server/src/map.rs b/minecraft-server/src/map.rs index 191cf344..a21acdd3 100644 --- a/minecraft-server/src/map.rs +++ b/minecraft-server/src/map.rs @@ -176,12 +176,12 @@ impl Chunk { struct HeightMap { base: u8, data: Vec, - max_height: Option, + max_height: Option, } impl HeightMap { pub fn new(base: u8) -> Self { - assert!(base <= 9); + assert!(base <= 9, "base must be <= 9 because the max height is 320 + 64"); Self { base, data: vec![0; ((16 * 16 * 9usize).div_euclid(base as usize) + 1) * base as usize ], @@ -191,16 +191,15 @@ impl HeightMap { /// Update the current base of the heightmap. fn new_base(&mut self, base: u8) { - + unimplemented!(); } - fn get_need_base(&self, height: i32) -> u8 { + fn get_need_base(&self, height: u32) -> u8 { 32 - ((height + 1).leading_zeros() as u8) } /// Set the height of the highest block at the given position. - pub fn set(&mut self, position: BlockPositionInChunkColumn) { + pub fn set(&mut self, position: BlockPositionInChunkColumn, height: u32) { let (x, z) = (position.bx, position.bz); - let height = position.y; // Check if the height is higher than the current max height. if let Some(max_height) = self.max_height { @@ -225,19 +224,15 @@ impl HeightMap { panic!("base too large for u64 storage"); } - // Calculate the signed height ensuring it doesn't overflow. - let signed_height = if bits_per_entry < 64 { - ((height as u64) << (64 - bits_per_entry)) >> (64 - bits_per_entry) - } else { - height as u64 - }; + // Cast the height to u64 + let height = height as u64; // Prepare the mask to clear the bits at the position. let mask = ((1 << bits_per_entry) - 1) << bit_offset; // Clear the bits at the target position. self.data[data_index] &= !mask; // Set the new height with the sign. - self.data[data_index] |= signed_height << bit_offset; + self.data[data_index] |= height << bit_offset; // Check if the entry spills over to the next u64. if bit_offset + bits_per_entry > 64 { // Calculate how many bits spill over. @@ -247,12 +242,12 @@ impl HeightMap { // Clear the spill over bits in the next entry. self.data[data_index + 1] &= !spill_over_mask; // Set the spill over bits. - self.data[data_index + 1] |= signed_height >> (64 - bit_offset); + self.data[data_index + 1] |= height >> (64 - bit_offset); } } /// Get the height of the highest block at the given position. - pub fn get(&self, position: BlockPositionInChunkColumn) -> i32 { + pub fn get(&self, position: BlockPositionInChunkColumn) -> u32 { let (x, z) = (position.bx, position.bz); let index = (x * 16 + z) as usize; // assuming a 16x16 chunk column @@ -284,7 +279,7 @@ impl HeightMap { } // Cast to i32 with sign extension. - value as i32 + value as u32 } } @@ -296,33 +291,29 @@ struct ChunkColumn { } impl ChunkColumn { - const MAX_HEIGHT: i32 = 320; // TODO: adapt to the world height + const MAX_HEIGHT: u32 = 320 + 64; // TODO: adapt to the world height fn init_chunk_heightmap(&mut self){ self.heightmap = HeightMap::new(9); if self.chunks.len() != 24 { - panic!("Chunk column must have 24 chunks"); + panic!("Chunk column must have 24 chunks (change it for other world heights)"); } // Start from the higher chunk for bx in 0..16 { for bz in 0..16 { let mut current_height = Self::MAX_HEIGHT; - 'chunks: for chunk in self.chunks.iter() { - while current_height >= 0 { - let block: BlockWithState = chunk.get_block(BlockPositionInChunk { bx, by: (current_height % 16) as u8, bz }); + 'chunks: for chunk in self.chunks.iter().rev() { + for by in (0..16).rev() { + let block: BlockWithState = chunk.get_block(BlockPositionInChunk { bx, by, bz }); // SAFETY: fom_id will get a valid block necessarily - if Block::from_id(block.block_id()).unwrap().is_air_block() { + if !Block::from_id(block.block_id()).unwrap().is_air_block() { break 'chunks; } current_height -= 1; - - if (current_height % 16) <= 0 { - break 'chunks; - } } } - self.heightmap.set(BlockPositionInChunkColumn { bx, y: current_height, bz }); + self.heightmap.set(BlockPositionInChunkColumn { bx, y: 0, bz }, current_height ); } } } @@ -555,8 +546,10 @@ mod tests { assert_eq!(high_block.block_state_id().unwrap(), BlockWithState::Air.block_state_id().unwrap()); // Check that the heightmap is correct + flat_column.set_block(BlockPositionInChunkColumn { bx: 0, y: 2, bz: 0 }, BlockWithState::Grass); + flat_column.init_chunk_heightmap(); let heightmap = &flat_column.heightmap; - assert_eq!(heightmap.get(BlockPositionInChunkColumn { bx: 0, y: 0, bz: 0 }), 120); + assert_eq!(heightmap.get(BlockPositionInChunkColumn { bx: 0, y: 0, bz: 0 }), 16); } #[tokio::test] @@ -600,21 +593,21 @@ mod tests { #[test] fn test_heightmap_get_and_set() { let mut heightmap = HeightMap::new(9); - heightmap.set(BlockPositionInChunkColumn { bx: 0, y: 0, bz: 0 }); - heightmap.set(BlockPositionInChunkColumn { bx: 0, y: -2, bz: 1 }); - heightmap.set(BlockPositionInChunkColumn { bx: 0, y: 3, bz: 2 }); - heightmap.set(BlockPositionInChunkColumn { bx: 0, y: -4, bz: 3 }); - heightmap.set(BlockPositionInChunkColumn { bx: 0, y: -4, bz: 7 }); + heightmap.set(BlockPositionInChunkColumn { bx: 0, y: 0, bz: 0 }, 0); + heightmap.set(BlockPositionInChunkColumn { bx: 0, y: -2, bz: 1 }, 2); + heightmap.set(BlockPositionInChunkColumn { bx: 0, y: 3, bz: 2 }, 3); + heightmap.set(BlockPositionInChunkColumn { bx: 0, y: -4, bz: 3 }, 4); + heightmap.set(BlockPositionInChunkColumn { bx: 0, y: -4, bz: 7 }, 5); // Test get assert_eq!(heightmap.get(BlockPositionInChunkColumn { bx: 0, y: 0, bz: 0 }), 0); - assert_eq!(heightmap.get(BlockPositionInChunkColumn { bx: 0, y: 0, bz: 1 }), -2); + assert_eq!(heightmap.get(BlockPositionInChunkColumn { bx: 0, y: 0, bz: 1 }), 2); assert_eq!(heightmap.get(BlockPositionInChunkColumn { bx: 0, y: 0, bz: 2 }), 3); - assert_eq!(heightmap.get(BlockPositionInChunkColumn { bx: 0, y: 0, bz: 3 }), -4); - assert_eq!(heightmap.get(BlockPositionInChunkColumn { bx: 0, y: 0, bz: 7 }), -4); + assert_eq!(heightmap.get(BlockPositionInChunkColumn { bx: 0, y: 0, bz: 3 }), 4); + assert_eq!(heightmap.get(BlockPositionInChunkColumn { bx: 0, y: 0, bz: 7 }), 5); // Test erase - heightmap.set(BlockPositionInChunkColumn { bx: 0, y: 12, bz: 0 }); + heightmap.set(BlockPositionInChunkColumn { bx: 0, y: 12, bz: 0 }, 12); assert_eq!(heightmap.get(BlockPositionInChunkColumn { bx: 0, y: 12, bz: 0 }), 12); } From d2b5de23c68e4984f37de4fe29864879cec6faae Mon Sep 17 00:00:00 2001 From: DimitriTimoz Date: Sat, 11 Nov 2023 02:29:23 +0100 Subject: [PATCH 08/46] New base & setter when edit block WIP --- minecraft-server/src/map.rs | 39 ++++++++++++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/minecraft-server/src/map.rs b/minecraft-server/src/map.rs index a21acdd3..7fc72b86 100644 --- a/minecraft-server/src/map.rs +++ b/minecraft-server/src/map.rs @@ -1,4 +1,4 @@ -use std::collections::HashMap; +use std::{collections::HashMap, cmp::Ordering}; use minecraft_protocol::{components::chunk::PalettedData, ids::blocks::Block}; use tokio::sync::RwLock; use crate::prelude::*; @@ -190,13 +190,21 @@ impl HeightMap { } /// Update the current base of the heightmap. - fn new_base(&mut self, base: u8) { + fn new_base(&mut self, new_base: u8) { + assert!(new_base <= 9, "base must be <= 9 because the max height is 320 + 64"); + + let old_base = self.base as usize; + unimplemented!(); + + self.base = new_base as u8; } + fn get_need_base(&self, height: u32) -> u8 { 32 - ((height + 1).leading_zeros() as u8) } + /// Set the height of the highest block at the given position. pub fn set(&mut self, position: BlockPositionInChunkColumn, height: u32) { let (x, z) = (position.bx, position.bz); @@ -369,6 +377,23 @@ impl ChunkColumn { chunk.set_block(position, block); Some(()) } + + let mut last_height = self.heightmap.get(position.clone()); + + // Get the height of the placed block + let mut block_height = (position.y + 1) as u32; + match block_height.cmp(&last_height) { + Ordering::Less => { + self.heightmap.set(position.clone(), block_height); + }, + Ordering::Greater => { + // Downward propagation + + + }, + Ordering::Equal => {} + } + set_block_innter(self, position, block); } } @@ -592,7 +617,7 @@ mod tests { #[test] fn test_heightmap_get_and_set() { - let mut heightmap = HeightMap::new(9); + let mut heightmap = HeightMap::new(5); heightmap.set(BlockPositionInChunkColumn { bx: 0, y: 0, bz: 0 }, 0); heightmap.set(BlockPositionInChunkColumn { bx: 0, y: -2, bz: 1 }, 2); heightmap.set(BlockPositionInChunkColumn { bx: 0, y: 3, bz: 2 }, 3); @@ -609,6 +634,14 @@ mod tests { // Test erase heightmap.set(BlockPositionInChunkColumn { bx: 0, y: 12, bz: 0 }, 12); assert_eq!(heightmap.get(BlockPositionInChunkColumn { bx: 0, y: 12, bz: 0 }), 12); + + // Test new base + heightmap.new_base(8); + assert_eq!(heightmap.get(BlockPositionInChunkColumn { bx: 0, y: 0, bz: 0 }), 0); + assert_eq!(heightmap.get(BlockPositionInChunkColumn { bx: 0, y: 0, bz: 1 }), 2); + assert_eq!(heightmap.get(BlockPositionInChunkColumn { bx: 0, y: 0, bz: 2 }), 3); + assert_eq!(heightmap.get(BlockPositionInChunkColumn { bx: 0, y: 0, bz: 3 }), 4); + assert_eq!(heightmap.get(BlockPositionInChunkColumn { bx: 0, y: 0, bz: 7 }), 5); } From 0263e4ee05a035d646e1d6b2de8983eb12c95252 Mon Sep 17 00:00:00 2001 From: DimitriTimoz Date: Sat, 11 Nov 2023 22:06:06 +0100 Subject: [PATCH 09/46] Add HeightMap is updated impl --- minecraft-server/src/map.rs | 129 ++++++++++++++++++++++-------------- 1 file changed, 79 insertions(+), 50 deletions(-) diff --git a/minecraft-server/src/map.rs b/minecraft-server/src/map.rs index 7fc72b86..c571c85b 100644 --- a/minecraft-server/src/map.rs +++ b/minecraft-server/src/map.rs @@ -1,5 +1,5 @@ use std::{collections::HashMap, cmp::Ordering}; -use minecraft_protocol::{components::chunk::PalettedData, ids::blocks::Block}; +use minecraft_protocol::{components::chunk::{PalettedData, self}, ids::blocks::Block}; use tokio::sync::RwLock; use crate::prelude::*; @@ -206,7 +206,7 @@ impl HeightMap { } /// Set the height of the highest block at the given position. - pub fn set(&mut self, position: BlockPositionInChunkColumn, height: u32) { + pub fn set(&mut self, position: &BlockPositionInChunkColumn, height: u32) { let (x, z) = (position.bx, position.bz); // Check if the height is higher than the current max height. @@ -255,7 +255,7 @@ impl HeightMap { } /// Get the height of the highest block at the given position. - pub fn get(&self, position: BlockPositionInChunkColumn) -> u32 { + pub fn get(&self, position: &BlockPositionInChunkColumn) -> u16 { let (x, z) = (position.bx, position.bz); let index = (x * 16 + z) as usize; // assuming a 16x16 chunk column @@ -287,7 +287,7 @@ impl HeightMap { } // Cast to i32 with sign extension. - value as u32 + value as u16 } } @@ -299,8 +299,9 @@ struct ChunkColumn { } impl ChunkColumn { - const MAX_HEIGHT: u32 = 320 + 64; // TODO: adapt to the world height - + const MAX_HEIGHT: u16 = 320 + 64; // TODO: adapt to the world height + const MIN_Y: i32 = -64; + fn init_chunk_heightmap(&mut self){ self.heightmap = HeightMap::new(9); if self.chunks.len() != 24 { @@ -310,22 +311,30 @@ impl ChunkColumn { // Start from the higher chunk for bx in 0..16 { for bz in 0..16 { - let mut current_height = Self::MAX_HEIGHT; - 'chunks: for chunk in self.chunks.iter().rev() { - for by in (0..16).rev() { - let block: BlockWithState = chunk.get_block(BlockPositionInChunk { bx, by, bz }); - // SAFETY: fom_id will get a valid block necessarily - if !Block::from_id(block.block_id()).unwrap().is_air_block() { - break 'chunks; - } - current_height -= 1; - } - } - self.heightmap.set(BlockPositionInChunkColumn { bx, y: 0, bz }, current_height ); + let height = self.get_higher_skylight_filter_block(&BlockPositionInChunkColumn { bx, y: 0, bz }, Self::MAX_HEIGHT).into(); + self.heightmap.set(&BlockPositionInChunkColumn { bx, y: 0, bz }, height); } } } + fn get_higher_skylight_filter_block(&self, position: &BlockPositionInChunkColumn, current_height: u16) -> u16 { + let n_chunk_to_skip = self.chunks.len() - current_height.div_euclid(16) as usize - (current_height.rem_euclid(16) > 0) as usize; + let mut current_height = current_height - 1; + // Downward propagation + for chunk in self.chunks.iter().rev().skip(n_chunk_to_skip) { + //println!("index: {:?}", (current_height % 16) as u8 + 1); + for by in (0..((((current_height) % 16) + 1) as u8)).rev() { + let block: BlockWithState = chunk.get_block(BlockPositionInChunk { bx: position.bx, by, bz: position.bz }); + // SAFETY: fom_id will get a valid block necessarily + if !Block::from_id(block.block_id()).unwrap().is_air_block() { + return current_height + 1; + } + current_height = current_height.saturating_sub(1); + } + } + current_height + } + pub fn from(chunks: Vec) -> Self { let mut column = Self { chunks, heightmap: HeightMap::new(9) }; column.init_chunk_heightmap(); @@ -378,20 +387,22 @@ impl ChunkColumn { Some(()) } - let mut last_height = self.heightmap.get(position.clone()); + let last_height = self.heightmap.get(&position); + let filter_sunlight = Block::from_id(block.block_id()).unwrap().is_air_block(); // TODO: check if the block is transparent // Get the height of the placed block - let mut block_height = (position.y + 1) as u32; + let block_height = (position.y - Self::MIN_Y).max(0) as u16; + match block_height.cmp(&last_height) { - Ordering::Less => { - self.heightmap.set(position.clone(), block_height); + Ordering::Greater if !filter_sunlight => { + self.heightmap.set(&position, block_height.into()); }, - Ordering::Greater => { + Ordering::Equal if filter_sunlight => { // Downward propagation - - + let new_height = self.get_higher_skylight_filter_block(&position, last_height).into(); + self.heightmap.set(&position, new_height); }, - Ordering::Equal => {} + _ => {} } set_block_innter(self, position, block); @@ -569,12 +580,6 @@ mod tests { let high_block = flat_column.get_block(BlockPositionInChunkColumn { bx: 0, y: 120, bz: 0 }); assert_eq!(high_block.block_state_id().unwrap(), BlockWithState::Air.block_state_id().unwrap()); - - // Check that the heightmap is correct - flat_column.set_block(BlockPositionInChunkColumn { bx: 0, y: 2, bz: 0 }, BlockWithState::Grass); - flat_column.init_chunk_heightmap(); - let heightmap = &flat_column.heightmap; - assert_eq!(heightmap.get(BlockPositionInChunkColumn { bx: 0, y: 0, bz: 0 }), 16); } #[tokio::test] @@ -618,30 +623,54 @@ mod tests { #[test] fn test_heightmap_get_and_set() { let mut heightmap = HeightMap::new(5); - heightmap.set(BlockPositionInChunkColumn { bx: 0, y: 0, bz: 0 }, 0); - heightmap.set(BlockPositionInChunkColumn { bx: 0, y: -2, bz: 1 }, 2); - heightmap.set(BlockPositionInChunkColumn { bx: 0, y: 3, bz: 2 }, 3); - heightmap.set(BlockPositionInChunkColumn { bx: 0, y: -4, bz: 3 }, 4); - heightmap.set(BlockPositionInChunkColumn { bx: 0, y: -4, bz: 7 }, 5); + heightmap.set(&BlockPositionInChunkColumn { bx: 0, y: 0, bz: 0 }, 0); + heightmap.set(&BlockPositionInChunkColumn { bx: 0, y: -2, bz: 1 }, 2); + heightmap.set(&BlockPositionInChunkColumn { bx: 0, y: 3, bz: 2 }, 3); + heightmap.set(&BlockPositionInChunkColumn { bx: 0, y: -4, bz: 3 }, 4); + heightmap.set(&BlockPositionInChunkColumn { bx: 0, y: -4, bz: 7 }, 5); // Test get - assert_eq!(heightmap.get(BlockPositionInChunkColumn { bx: 0, y: 0, bz: 0 }), 0); - assert_eq!(heightmap.get(BlockPositionInChunkColumn { bx: 0, y: 0, bz: 1 }), 2); - assert_eq!(heightmap.get(BlockPositionInChunkColumn { bx: 0, y: 0, bz: 2 }), 3); - assert_eq!(heightmap.get(BlockPositionInChunkColumn { bx: 0, y: 0, bz: 3 }), 4); - assert_eq!(heightmap.get(BlockPositionInChunkColumn { bx: 0, y: 0, bz: 7 }), 5); + assert_eq!(heightmap.get(&BlockPositionInChunkColumn { bx: 0, y: 0, bz: 0 }), 0); + assert_eq!(heightmap.get(&BlockPositionInChunkColumn { bx: 0, y: 0, bz: 1 }), 2); + assert_eq!(heightmap.get(&BlockPositionInChunkColumn { bx: 0, y: 0, bz: 2 }), 3); + assert_eq!(heightmap.get(&BlockPositionInChunkColumn { bx: 0, y: 0, bz: 3 }), 4); + assert_eq!(heightmap.get(&BlockPositionInChunkColumn { bx: 0, y: 0, bz: 7 }), 5); // Test erase - heightmap.set(BlockPositionInChunkColumn { bx: 0, y: 12, bz: 0 }, 12); - assert_eq!(heightmap.get(BlockPositionInChunkColumn { bx: 0, y: 12, bz: 0 }), 12); + heightmap.set(&BlockPositionInChunkColumn { bx: 0, y: 12, bz: 0 }, 12); + assert_eq!(heightmap.get(&BlockPositionInChunkColumn { bx: 0, y: 12, bz: 0 }), 12); // Test new base - heightmap.new_base(8); - assert_eq!(heightmap.get(BlockPositionInChunkColumn { bx: 0, y: 0, bz: 0 }), 0); - assert_eq!(heightmap.get(BlockPositionInChunkColumn { bx: 0, y: 0, bz: 1 }), 2); - assert_eq!(heightmap.get(BlockPositionInChunkColumn { bx: 0, y: 0, bz: 2 }), 3); - assert_eq!(heightmap.get(BlockPositionInChunkColumn { bx: 0, y: 0, bz: 3 }), 4); - assert_eq!(heightmap.get(BlockPositionInChunkColumn { bx: 0, y: 0, bz: 7 }), 5); + //heightmap.new_base(8); + assert_eq!(heightmap.get(&BlockPositionInChunkColumn { bx: 0, y: 0, bz: 0 }), 12); + assert_eq!(heightmap.get(&BlockPositionInChunkColumn { bx: 0, y: 0, bz: 1 }), 2); + assert_eq!(heightmap.get(&BlockPositionInChunkColumn { bx: 0, y: 0, bz: 2 }), 3); + assert_eq!(heightmap.get(&BlockPositionInChunkColumn { bx: 0, y: 0, bz: 3 }), 4); + assert_eq!(heightmap.get(&BlockPositionInChunkColumn { bx: 0, y: 0, bz: 7 }), 5); + } + + #[test] + fn test_heightmap_auto_updates() { + let mut flat_column = ChunkColumn::flat(); + + // Check that the heightmap is correct + flat_column.set_block(BlockPositionInChunkColumn { bx: 0, y: 2, bz: 0 }, BlockWithState::GrassBlock { snowy: true }); + flat_column.init_chunk_heightmap(); + assert_eq!(flat_column.heightmap.get(&BlockPositionInChunkColumn { bx: 0, y: 0, bz: 0 }), 67); + assert_eq!(flat_column.heightmap.get(&BlockPositionInChunkColumn { bx: 0, y: 0, bz: 1 }), 16); + + // Now check that the heightmap is correct after setting a block + flat_column.set_block(BlockPositionInChunkColumn { bx: 0, y: 10, bz: 0 }, BlockWithState::GrassBlock { snowy: false }); + assert_eq!(flat_column.heightmap.get(&BlockPositionInChunkColumn { bx: 0, y: 0, bz: 0 }), 74); + + // Check that the heightmap is correct after setting a block to air under the highest block + flat_column.set_block(BlockPositionInChunkColumn { bx: 0, y: 8, bz: 0 }, BlockWithState::Air); + assert_eq!(flat_column.heightmap.get(&BlockPositionInChunkColumn { bx: 0, y: 0, bz: 0 }), 74); + + // Check that the heightmap is correct after setting the highest block to air + flat_column.set_block(BlockPositionInChunkColumn { bx: 0, y: 10, bz: 0 }, BlockWithState::Air); + assert_eq!(flat_column.heightmap.get(&BlockPositionInChunkColumn { bx: 0, y: 0, bz: 0 }), 67); + } From 12a5ec409d2f9f6abb213a95558e96efc5010602 Mon Sep 17 00:00:00 2001 From: DimitriTimoz Date: Sat, 11 Nov 2023 22:44:53 +0100 Subject: [PATCH 10/46] Add SectionLightData structure & setter/getter --- minecraft-server/src/light.rs | 68 +++++++++++++++++++++++++++++++++++ minecraft-server/src/main.rs | 1 + 2 files changed, 69 insertions(+) create mode 100644 minecraft-server/src/light.rs diff --git a/minecraft-server/src/light.rs b/minecraft-server/src/light.rs new file mode 100644 index 00000000..ed3b1554 --- /dev/null +++ b/minecraft-server/src/light.rs @@ -0,0 +1,68 @@ +use crate::prelude::*; + + +#[derive(Debug, Clone)] +pub struct SectionLightData(Vec); + +impl SectionLightData { + pub fn new() -> SectionLightData { + SectionLightData(vec![0; 2048]) + } + + /// Get the light level at the given position. + pub fn get(&self, postion: ChunkPosition) -> u8 { + let (x, y, z) = (postion.cx as usize, postion.cy as usize, postion.cz as usize); + let index = (y << 4) | (z << 4) | x; + let byte_index = index >> 1; + + assert!(byte_index < 2048); + + if index & 1 == 0 { + self.0[byte_index] & 0x0F + } else { + (self.0[byte_index] & 0xF0) >> 4 + } + } + + /// Set the light level at the given position. + pub fn set(&mut self, postion: ChunkPosition, level: u8) { + let (x, y, z) = (postion.cx as usize, postion.cy as usize, postion.cz as usize); + let index = (y << 4) | (z << 4) | x; + let byte_index = index >> 1; + + if index & 1 == 0 { + self.0[byte_index] = (self.0[byte_index] & 0xF0) | (level & 0x0F); + } else { + self.0[byte_index] = (self.0[byte_index] & 0x0F) | ((level & 0x0F) << 4); + } + } +} + +#[derive(Debug, Clone)] +pub struct SkyLight { + /// The level of the sky light, 15 is the maximum. + pub level: u8, + /// The sky light data for each section. + pub sky_light_arrays: Vec, + /// The mask of sections that have sky light data. + pub sky_light_mask: u64, + /// The mask of sections that don't have sky light data. + pub empty_sky_light_mask: u64, +} + +pub struct Light { + pub sky_light: SkyLight, +} + +impl Light { + pub fn new() -> Self { + Self { + sky_light: SkyLight { + level: 15, + sky_light_arrays: vec![SectionLightData::new(); 16], + sky_light_mask: 0, + empty_sky_light_mask: !0, + } + } + } +} diff --git a/minecraft-server/src/main.rs b/minecraft-server/src/main.rs index f3a4cce1..f60021eb 100644 --- a/minecraft-server/src/main.rs +++ b/minecraft-server/src/main.rs @@ -8,6 +8,7 @@ mod entities; mod map; mod ecs; mod world; +mod light; use std::env; use crate::prelude::*; From a3ecae9c7e05c1b2cb255451595be5553423fb4b Mon Sep 17 00:00:00 2001 From: DimitriTimoz Date: Sat, 11 Nov 2023 22:58:23 +0100 Subject: [PATCH 11/46] sky light init done --- minecraft-server/src/light.rs | 14 ++++++++++---- minecraft-server/src/map.rs | 13 +++++++++---- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/minecraft-server/src/light.rs b/minecraft-server/src/light.rs index ed3b1554..f988e8c4 100644 --- a/minecraft-server/src/light.rs +++ b/minecraft-server/src/light.rs @@ -2,7 +2,7 @@ use crate::prelude::*; #[derive(Debug, Clone)] -pub struct SectionLightData(Vec); +struct SectionLightData(Vec); impl SectionLightData { pub fn new() -> SectionLightData { @@ -39,7 +39,7 @@ impl SectionLightData { } #[derive(Debug, Clone)] -pub struct SkyLight { +struct SkyLight { /// The level of the sky light, 15 is the maximum. pub level: u8, /// The sky light data for each section. @@ -50,8 +50,8 @@ pub struct SkyLight { pub empty_sky_light_mask: u64, } -pub struct Light { - pub sky_light: SkyLight, +pub(super) struct Light { + sky_light: SkyLight, } impl Light { @@ -66,3 +66,9 @@ impl Light { } } } + +impl ChunkColumn { + pub fn propagate_light_inside(&mut self) { + + } +} diff --git a/minecraft-server/src/map.rs b/minecraft-server/src/map.rs index c571c85b..2b1ade3c 100644 --- a/minecraft-server/src/map.rs +++ b/minecraft-server/src/map.rs @@ -1,7 +1,7 @@ use std::{collections::HashMap, cmp::Ordering}; use minecraft_protocol::{components::chunk::{PalettedData, self}, ids::blocks::Block}; use tokio::sync::RwLock; -use crate::prelude::*; +use crate::{prelude::*, light::Light}; pub struct WorldMap { /// The map is divided in shards. @@ -19,7 +19,7 @@ impl ChunkColumnPosition { } #[derive(Clone)] -struct Chunk { +pub(super) struct Chunk { data: NetworkChunk, palette_block_counts: Vec, } @@ -293,8 +293,9 @@ impl HeightMap { } -struct ChunkColumn { +pub(super) struct ChunkColumn { heightmap: HeightMap, + light: Light, chunks: Vec, } @@ -336,7 +337,11 @@ impl ChunkColumn { } pub fn from(chunks: Vec) -> Self { - let mut column = Self { chunks, heightmap: HeightMap::new(9) }; + let mut column = Self { + chunks, + heightmap: HeightMap::new(9), + light: Light::new(), + }; column.init_chunk_heightmap(); column } From 1a57c642e3ab00250cd4e14c34375fae878c34ac Mon Sep 17 00:00:00 2001 From: DimitriTimoz Date: Sat, 11 Nov 2023 23:06:45 +0100 Subject: [PATCH 12/46] Change is_air_block to is_transparent --- minecraft-server/src/map.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/minecraft-server/src/map.rs b/minecraft-server/src/map.rs index 2b1ade3c..94b9e764 100644 --- a/minecraft-server/src/map.rs +++ b/minecraft-server/src/map.rs @@ -327,7 +327,7 @@ impl ChunkColumn { for by in (0..((((current_height) % 16) + 1) as u8)).rev() { let block: BlockWithState = chunk.get_block(BlockPositionInChunk { bx: position.bx, by, bz: position.bz }); // SAFETY: fom_id will get a valid block necessarily - if !Block::from_id(block.block_id()).unwrap().is_air_block() { + if !Block::from_id(block.block_id()).unwrap().is_transparent() { return current_height + 1; } current_height = current_height.saturating_sub(1); @@ -393,7 +393,7 @@ impl ChunkColumn { } let last_height = self.heightmap.get(&position); - let filter_sunlight = Block::from_id(block.block_id()).unwrap().is_air_block(); // TODO: check if the block is transparent + let filter_sunlight = Block::from_id(block.block_id()).unwrap().is_transparent(); // TODO: check if the block is transparent // Get the height of the placed block let block_height = (position.y - Self::MIN_Y).max(0) as u16; From 97a77a1883a4e87e3fff242006317ca403b3193d Mon Sep 17 00:00:00 2001 From: DimitriTimoz Date: Sun, 12 Nov 2023 00:06:37 +0100 Subject: [PATCH 13/46] Add Test for set and getter --- minecraft-server/src/light.rs | 49 ++++++++++++++++++++++++++++++----- minecraft-server/src/map.rs | 4 +++ 2 files changed, 46 insertions(+), 7 deletions(-) diff --git a/minecraft-server/src/light.rs b/minecraft-server/src/light.rs index f988e8c4..8fa46548 100644 --- a/minecraft-server/src/light.rs +++ b/minecraft-server/src/light.rs @@ -1,3 +1,5 @@ +use std::{collections::BinaryHeap, cmp::Ordering}; + use crate::prelude::*; @@ -10,8 +12,8 @@ impl SectionLightData { } /// Get the light level at the given position. - pub fn get(&self, postion: ChunkPosition) -> u8 { - let (x, y, z) = (postion.cx as usize, postion.cy as usize, postion.cz as usize); + pub fn get(&self, postion: BlockPositionInChunk) -> u8 { + let (x, y, z) = (postion.bx as usize, postion.by as usize, postion.bz as usize); let index = (y << 4) | (z << 4) | x; let byte_index = index >> 1; @@ -25,8 +27,8 @@ impl SectionLightData { } /// Set the light level at the given position. - pub fn set(&mut self, postion: ChunkPosition, level: u8) { - let (x, y, z) = (postion.cx as usize, postion.cy as usize, postion.cz as usize); + pub fn set(&mut self, postion: BlockPositionInChunk, level: u8) { + let (x, y, z) = (postion.bx as usize, postion.by as usize, postion.bz as usize); let index = (y << 4) | (z << 4) | x; let byte_index = index >> 1; @@ -36,6 +38,19 @@ impl SectionLightData { self.0[byte_index] = (self.0[byte_index] & 0x0F) | ((level & 0x0F) << 4); } } + + /// Set the light level at the given slice to the given level. + pub(super) fn set_slice(&mut self, slice: u8 , level: u8) { + let slice_index = (slice as usize) << 4; + let level_byte = level << 4 | level; + for z in 0..16 { + let z_index = slice_index | (z << 4); + for x in 0..8 { + let index = z_index | x; + self.0[index] = level_byte; + } + } + } } #[derive(Debug, Clone)] @@ -67,8 +82,28 @@ impl Light { } } -impl ChunkColumn { - pub fn propagate_light_inside(&mut self) { - +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_section_light_data() { + let mut data = SectionLightData::new(); + + data.set(BlockPositionInChunk { bx: 0, by: 0, bz: 0 }, 15); + assert_eq!(data.get(BlockPositionInChunk { bx: 0, by: 0, bz: 0 }), 15); + + data.set(BlockPositionInChunk { bx: 0, by: 0, bz: 0 }, 0); + assert_eq!(data.get(BlockPositionInChunk { bx: 0, by: 0, bz: 0 }), 0); + + data.set(BlockPositionInChunk { bx: 0, by: 0, bz: 1 }, 1); + assert_eq!(data.get(BlockPositionInChunk { bx: 0, by: 0, bz: 1 }), 1); + + data.set(BlockPositionInChunk { bx: 0, by: 1, bz: 1 }, 15); + assert_eq!(data.get(BlockPositionInChunk { bx: 0, by: 1, bz: 1 }), 15); + + data.set(BlockPositionInChunk { bx: 1, by: 1, bz: 1 }, 1); + assert_eq!(data.get(BlockPositionInChunk { bx: 1, by: 1, bz: 1 }), 1); + } } diff --git a/minecraft-server/src/map.rs b/minecraft-server/src/map.rs index 94b9e764..223f67da 100644 --- a/minecraft-server/src/map.rs +++ b/minecraft-server/src/map.rs @@ -336,6 +336,10 @@ impl ChunkColumn { current_height } + pub(super) fn get_highest_block(&self) -> u32 { + self.heightmap.max_height.unwrap_or(0) as u32 + } + pub fn from(chunks: Vec) -> Self { let mut column = Self { chunks, From 635cef58ffd9a0a66d14c23095636dfa1a823835 Mon Sep 17 00:00:00 2001 From: DimitriTimoz Date: Sun, 12 Nov 2023 00:52:01 +0100 Subject: [PATCH 14/46] Set Layer of light --- minecraft-server/src/light.rs | 79 +++++++++++++++++++++++------------ 1 file changed, 52 insertions(+), 27 deletions(-) diff --git a/minecraft-server/src/light.rs b/minecraft-server/src/light.rs index 8fa46548..05cee81f 100644 --- a/minecraft-server/src/light.rs +++ b/minecraft-server/src/light.rs @@ -12,24 +12,30 @@ impl SectionLightData { } /// Get the light level at the given position. - pub fn get(&self, postion: BlockPositionInChunk) -> u8 { + pub fn get(&self, postion: BlockPositionInChunk) -> Result { let (x, y, z) = (postion.bx as usize, postion.by as usize, postion.bz as usize); - let index = (y << 4) | (z << 4) | x; + let index = (y << 8) | (z << 4) | x; let byte_index = index >> 1; - assert!(byte_index < 2048); + if byte_index >= 2048 { + return Err(()); + } if index & 1 == 0 { - self.0[byte_index] & 0x0F + Ok(self.0[byte_index] & 0x0F) } else { - (self.0[byte_index] & 0xF0) >> 4 + Ok((self.0[byte_index] & 0xF0) >> 4) } } /// Set the light level at the given position. - pub fn set(&mut self, postion: BlockPositionInChunk, level: u8) { + pub fn set(&mut self, postion: BlockPositionInChunk, level: u8) -> Result<(), ()> { + if level > 15 { + return Err(()); + } + let (x, y, z) = (postion.bx as usize, postion.by as usize, postion.bz as usize); - let index = (y << 4) | (z << 4) | x; + let index = (y << 8) | (z << 4) | x; let byte_index = index >> 1; if index & 1 == 0 { @@ -39,16 +45,11 @@ impl SectionLightData { } } - /// Set the light level at the given slice to the given level. - pub(super) fn set_slice(&mut self, slice: u8 , level: u8) { - let slice_index = (slice as usize) << 4; - let level_byte = level << 4 | level; - for z in 0..16 { - let z_index = slice_index | (z << 4); - for x in 0..8 { - let index = z_index | x; - self.0[index] = level_byte; - } + /// Set the light level at the given layer to the given level. + pub(super) fn set_layer(&mut self, layer: u8 , level: u8) -> Result<(), ()> { + if level > 15 { + return Err(()); + } } } } @@ -90,20 +91,44 @@ mod tests { fn test_section_light_data() { let mut data = SectionLightData::new(); - data.set(BlockPositionInChunk { bx: 0, by: 0, bz: 0 }, 15); - assert_eq!(data.get(BlockPositionInChunk { bx: 0, by: 0, bz: 0 }), 15); + data.set(BlockPositionInChunk { bx: 0, by: 0, bz: 0 }, 15).unwrap(); + assert_eq!(data.get(BlockPositionInChunk { bx: 0, by: 0, bz: 0 }).unwrap(), 15); + + data.set(BlockPositionInChunk { bx: 0, by: 0, bz: 0 }, 0).unwrap(); + assert_eq!(data.get(BlockPositionInChunk { bx: 0, by: 0, bz: 0 }).unwrap(), 0); + + data.set(BlockPositionInChunk { bx: 0, by: 0, bz: 1 }, 1).unwrap(); + assert_eq!(data.get(BlockPositionInChunk { bx: 0, by: 0, bz: 1 }).unwrap(), 1); + + data.set(BlockPositionInChunk { bx: 0, by: 1, bz: 1 }, 15).unwrap(); + assert_eq!(data.get(BlockPositionInChunk { bx: 0, by: 1, bz: 1 }).unwrap(), 15); - data.set(BlockPositionInChunk { bx: 0, by: 0, bz: 0 }, 0); - assert_eq!(data.get(BlockPositionInChunk { bx: 0, by: 0, bz: 0 }), 0); + data.set(BlockPositionInChunk { bx: 1, by: 1, bz: 1 }, 1).unwrap(); + assert_eq!(data.get(BlockPositionInChunk { bx: 1, by: 1, bz: 1 }).unwrap(), 1); - data.set(BlockPositionInChunk { bx: 0, by: 0, bz: 1 }, 1); - assert_eq!(data.get(BlockPositionInChunk { bx: 0, by: 0, bz: 1 }), 1); + data.set(BlockPositionInChunk { bx: 2, by: 0, bz: 0 }, 1).unwrap(); + assert_eq!(data.get(BlockPositionInChunk { bx: 2, by: 0, bz: 0 }).unwrap(), 1); - data.set(BlockPositionInChunk { bx: 0, by: 1, bz: 1 }, 15); - assert_eq!(data.get(BlockPositionInChunk { bx: 0, by: 1, bz: 1 }), 15); + // Manual layer + for z in 0..16 { + for x in 0..16 { + data.set(BlockPositionInChunk { bx: x, by: 0, bz: z }, 15).unwrap(); + } + } - data.set(BlockPositionInChunk { bx: 1, by: 1, bz: 1 }, 1); - assert_eq!(data.get(BlockPositionInChunk { bx: 1, by: 1, bz: 1 }), 1); + for z in 0..16 { + for x in 0..16 { + assert_eq!(data.get(BlockPositionInChunk { bx: x, by: 0, bz: z }).unwrap(), 15, "x: {}, z: {}", x, z); + } + } + + // Test layer + data.set_layer(1, 15).unwrap(); + for x in 0..16 { + for z in 0..16 { + assert_eq!(data.get(BlockPositionInChunk { bx: x, by: 1, bz: z }).unwrap(), 15, "x: {}, z: {}", x, z); + } + } } } From 42a73101bf4d26ade3632dfd2a67c034fbfd3e48 Mon Sep 17 00:00:00 2001 From: DimitriTimoz Date: Sun, 12 Nov 2023 01:21:27 +0100 Subject: [PATCH 15/46] Set region & more --- minecraft-server/src/light.rs | 128 ++++++++++++++++++++++++++++++++++ 1 file changed, 128 insertions(+) diff --git a/minecraft-server/src/light.rs b/minecraft-server/src/light.rs index 05cee81f..e03ddf9e 100644 --- a/minecraft-server/src/light.rs +++ b/minecraft-server/src/light.rs @@ -11,6 +11,12 @@ impl SectionLightData { SectionLightData(vec![0; 2048]) } + pub fn set_with(&mut self, level: u8) { + let level = level << 4 | level; + self.0.iter_mut().for_each(|v| *v = level); + } + + /// Get the light level at the given position. pub fn get(&self, postion: BlockPositionInChunk) -> Result { let (x, y, z) = (postion.bx as usize, postion.by as usize, postion.bz as usize); @@ -37,12 +43,18 @@ impl SectionLightData { let (x, y, z) = (postion.bx as usize, postion.by as usize, postion.bz as usize); let index = (y << 8) | (z << 4) | x; let byte_index = index >> 1; + + if byte_index >= 2048 { + return Err(()); + } if index & 1 == 0 { self.0[byte_index] = (self.0[byte_index] & 0xF0) | (level & 0x0F); } else { self.0[byte_index] = (self.0[byte_index] & 0x0F) | ((level & 0x0F) << 4); } + + Ok(()) } /// Set the light level at the given layer to the given level. @@ -50,7 +62,20 @@ impl SectionLightData { if level > 15 { return Err(()); } + + if layer > 15 { + return Err(()); + } + + let level = level << 4 | level; + let layer = layer as usize; + + // Because a layer is 16x16 blocks, we can just set 128 blocks at once and the y coordinate is the most significant bit of the index. + for i in layer*128..(layer+1)*128 { + self.0[i] = level; } + + Ok(()) } } @@ -64,6 +89,39 @@ struct SkyLight { pub sky_light_mask: u64, /// The mask of sections that don't have sky light data. pub empty_sky_light_mask: u64, + zero_chunk_index: usize, +} + +impl SkyLight { + /// Set the sky light in the given section. + pub fn set_region(&mut self, from_y: i32, to_y: i32, level: u8) -> Result<(), ()> { + if level > self.level { + return Err(()); + } + + // Get the range of sections to set. + let first_section = (from_y.div_euclid(16) + self.zero_chunk_index as i32).max(0) as usize; + let first_secion_offset = from_y.rem_euclid(16) as usize; + + let last_section = (to_y.div_euclid(16) + self.zero_chunk_index as i32).max(0) as usize; + let last_section_offset = to_y.rem_euclid(16) as usize; + + for section in first_section..=last_section { + if section != first_section && section != last_section { + // Set the whole section + self.sky_light_arrays[section].set_with(level); + } else { + // Set the part of the section + let first_offset = if section == first_section { first_secion_offset } else { 0 }; + let last_offset = if section == last_section { last_section_offset } else { 15 }; + for y in first_offset..=last_offset { + self.sky_light_arrays[section].set_layer(y as u8, level)?; + } + } + } + + Ok(()) + } } pub(super) struct Light { @@ -78,6 +136,53 @@ impl Light { sky_light_arrays: vec![SectionLightData::new(); 16], sky_light_mask: 0, empty_sky_light_mask: !0, + zero_chunk_index: 4, // We start at y=-64, and we have a chunk under that. + }, + } + } +} + +struct HeightBasedPosition { + x: u8, + y: usize, + z: u8, +} + +impl PartialEq for HeightBasedPosition { + fn eq(&self, other: &Self) -> bool { + self.y == other.y + } +} + +impl std::cmp::Eq for HeightBasedPosition {} + +impl std::cmp::PartialOrd for HeightBasedPosition { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.y.cmp(&other.y)) + } +} + +impl std::cmp::Ord for HeightBasedPosition { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.y.cmp(&other.y) + } +} + +impl ChunkColumn { + pub fn propagate_sky_light_inside(&mut self) { + // Set all highest blocks to the highest block + let highest_blocks = self.get_highest_block(); + + let n_chunk_with_sky_light = highest_blocks; + + + let mut to_explore: BinaryHeap = BinaryHeap::new(); + + + // Add all highest blocks to the queue + for x in 0..16 { + for z in 0..16 { + } } } } @@ -129,6 +234,29 @@ mod tests { assert_eq!(data.get(BlockPositionInChunk { bx: x, by: 1, bz: z }).unwrap(), 15, "x: {}, z: {}", x, z); } } + } + + #[test] + fn test_set_region() { + let mut sky_light = SkyLight { + level: 15, + sky_light_arrays: vec![SectionLightData::new(); 16+2], + sky_light_mask: 0, + empty_sky_light_mask: !0, + zero_chunk_index: 4, // We start at y=-64, and we have a chunk under that. + }; + + sky_light.set_region(-1, 16, 15).unwrap(); + + // Test in + assert_eq!(sky_light.sky_light_arrays[5].get(BlockPositionInChunk { bx: 0, by: 0, bz: 7 }).unwrap(), 15); + assert_eq!(sky_light.sky_light_arrays[4].get(BlockPositionInChunk { bx: 1, by: 2, bz: 8 }).unwrap(), 15); + assert_eq!(sky_light.sky_light_arrays[4].get(BlockPositionInChunk { bx: 3, by: 0, bz: 0 }).unwrap(), 15); + + // Test out + assert_eq!(sky_light.sky_light_arrays[5].get(BlockPositionInChunk { bx: 4, by: 1, bz: 2 }).unwrap(), 0); + assert_eq!(sky_light.sky_light_arrays[3].get(BlockPositionInChunk { bx: 0, by: 14, bz: 9 }).unwrap(), 0); + assert_eq!(sky_light.sky_light_arrays[0].get(BlockPositionInChunk { bx: 9, by: 0, bz: 10 }).unwrap(), 0); } } From c4b4207d2e8df5de5ec8704385a6cae91cbb79ba Mon Sep 17 00:00:00 2001 From: DimitriTimoz Date: Sun, 12 Nov 2023 02:07:50 +0100 Subject: [PATCH 16/46] Init skylight FlatChunk --- minecraft-server/src/light.rs | 49 ++++++++++++++++++++++++++++++----- minecraft-server/src/map.rs | 5 ++-- 2 files changed, 45 insertions(+), 9 deletions(-) diff --git a/minecraft-server/src/light.rs b/minecraft-server/src/light.rs index e03ddf9e..29132fdd 100644 --- a/minecraft-server/src/light.rs +++ b/minecraft-server/src/light.rs @@ -1,4 +1,4 @@ -use std::{collections::BinaryHeap, cmp::Ordering}; +use std::collections::BinaryHeap; use crate::prelude::*; @@ -99,6 +99,7 @@ impl SkyLight { return Err(()); } + println!("Set region from {} to {} with level {}", from_y, to_y, level); // Get the range of sections to set. let first_section = (from_y.div_euclid(16) + self.zero_chunk_index as i32).max(0) as usize; let first_secion_offset = from_y.rem_euclid(16) as usize; @@ -110,14 +111,25 @@ impl SkyLight { if section != first_section && section != last_section { // Set the whole section self.sky_light_arrays[section].set_with(level); + println!("Set section {}", section); } else { + println!("Set part of section {}", section); // Set the part of the section let first_offset = if section == first_section { first_secion_offset } else { 0 }; let last_offset = if section == last_section { last_section_offset } else { 15 }; for y in first_offset..=last_offset { + println!("Set layer {}", y); self.sky_light_arrays[section].set_layer(y as u8, level)?; } } + + // Update the mask + let mask = 1 << section; + if self.level > 0 { + self.empty_sky_light_mask |= mask; + } else { + self.empty_sky_light_mask &= !mask; + } } Ok(()) @@ -128,12 +140,14 @@ pub(super) struct Light { sky_light: SkyLight, } + impl Light { pub fn new() -> Self { + // TODO: Make this configurable with the world. Self { sky_light: SkyLight { level: 15, - sky_light_arrays: vec![SectionLightData::new(); 16], + sky_light_arrays: vec![SectionLightData::new(); 24+2], sky_light_mask: 0, empty_sky_light_mask: !0, zero_chunk_index: 4, // We start at y=-64, and we have a chunk under that. @@ -169,13 +183,17 @@ impl std::cmp::Ord for HeightBasedPosition { } impl ChunkColumn { - pub fn propagate_sky_light_inside(&mut self) { + pub(super) fn init_light(&mut self) -> Result<(), ()>{ + self.propagate_sky_light_inside()?; + Ok(()) + } + + fn propagate_sky_light_inside(&mut self) -> Result<(), ()> { // Set all highest blocks to the highest block let highest_blocks = self.get_highest_block(); - let n_chunk_with_sky_light = highest_blocks; - - + let max_y = (self.light.sky_light.sky_light_arrays.len() as i32 - self.light.sky_light.zero_chunk_index as i32) * 16 - 1; + self.light.sky_light.set_region(highest_blocks as i32 - 64, max_y, self.light.sky_light.level)?; let mut to_explore: BinaryHeap = BinaryHeap::new(); @@ -183,8 +201,12 @@ impl ChunkColumn { for x in 0..16 { for z in 0..16 { } - } } + + while let Some(position) = to_explore.iter().next() { + + } + Ok(()) } } @@ -257,6 +279,19 @@ mod tests { assert_eq!(sky_light.sky_light_arrays[5].get(BlockPositionInChunk { bx: 4, by: 1, bz: 2 }).unwrap(), 0); assert_eq!(sky_light.sky_light_arrays[3].get(BlockPositionInChunk { bx: 0, by: 14, bz: 9 }).unwrap(), 0); assert_eq!(sky_light.sky_light_arrays[0].get(BlockPositionInChunk { bx: 9, by: 0, bz: 10 }).unwrap(), 0); + } + + #[test] + fn test_sky_light_flat_chunk() { + let mut flat_chunk = ChunkColumn::flat(); + // Check that the sky light is equal to the light level above the grass and on the top of the world. + for x in 0..16 { + for z in 0..16 { + assert_eq!(flat_chunk.light.sky_light.sky_light_arrays[4].get(BlockPositionInChunk { bx: x, by: 0, bz: z }).unwrap(), 15); + assert_eq!(flat_chunk.light.sky_light.sky_light_arrays[25].get(BlockPositionInChunk { bx: x, by: 15, bz: z }).unwrap(), 15); + } + } + // Check that the sky light is under the grass } } diff --git a/minecraft-server/src/map.rs b/minecraft-server/src/map.rs index 223f67da..8ee7df9d 100644 --- a/minecraft-server/src/map.rs +++ b/minecraft-server/src/map.rs @@ -295,7 +295,7 @@ impl HeightMap { pub(super) struct ChunkColumn { heightmap: HeightMap, - light: Light, + pub(super) light: Light, chunks: Vec, } @@ -337,7 +337,7 @@ impl ChunkColumn { } pub(super) fn get_highest_block(&self) -> u32 { - self.heightmap.max_height.unwrap_or(0) as u32 + self.heightmap.max_height.unwrap_or(0) } pub fn from(chunks: Vec) -> Self { @@ -347,6 +347,7 @@ impl ChunkColumn { light: Light::new(), }; column.init_chunk_heightmap(); + let _ = column.init_light().map_err(|_| error!("Failed to init light in chunk column")); column } From 2b461551f40332c9ceb0a73964703fb87bfa038d Mon Sep 17 00:00:00 2001 From: DimitriTimoz Date: Sun, 12 Nov 2023 03:29:53 +0100 Subject: [PATCH 17/46] Add get_hiest_block_at --- minecraft-server/src/map.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/minecraft-server/src/map.rs b/minecraft-server/src/map.rs index 8ee7df9d..4f1524dd 100644 --- a/minecraft-server/src/map.rs +++ b/minecraft-server/src/map.rs @@ -340,6 +340,10 @@ impl ChunkColumn { self.heightmap.max_height.unwrap_or(0) } + pub(super) fn get_hiest_block_at(&self, position: &BlockPositionInChunkColumn) -> u16 { + self.heightmap.get(position) + } + pub fn from(chunks: Vec) -> Self { let mut column = Self { chunks, From 6bbf96ae80f0095d4ea437f17e24abcdbeee79bb Mon Sep 17 00:00:00 2001 From: DimitriTimoz Date: Sun, 12 Nov 2023 03:31:01 +0100 Subject: [PATCH 18/46] Use more appropriate conversion to Block --- minecraft-server/src/map.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/minecraft-server/src/map.rs b/minecraft-server/src/map.rs index 4f1524dd..f5ad0311 100644 --- a/minecraft-server/src/map.rs +++ b/minecraft-server/src/map.rs @@ -327,7 +327,7 @@ impl ChunkColumn { for by in (0..((((current_height) % 16) + 1) as u8)).rev() { let block: BlockWithState = chunk.get_block(BlockPositionInChunk { bx: position.bx, by, bz: position.bz }); // SAFETY: fom_id will get a valid block necessarily - if !Block::from_id(block.block_id()).unwrap().is_transparent() { + if !Block::from(block).is_transparent() { return current_height + 1; } current_height = current_height.saturating_sub(1); @@ -402,7 +402,7 @@ impl ChunkColumn { } let last_height = self.heightmap.get(&position); - let filter_sunlight = Block::from_id(block.block_id()).unwrap().is_transparent(); // TODO: check if the block is transparent + let filter_sunlight = Block::from(block.clone()).is_transparent(); // TODO: check if the block is transparent // Get the height of the placed block let block_height = (position.y - Self::MIN_Y).max(0) as u16; From ad61f59858bccdc963be9a40502a0811dc8d7921 Mon Sep 17 00:00:00 2001 From: DimitriTimoz Date: Sun, 12 Nov 2023 03:38:46 +0100 Subject: [PATCH 19/46] WIP explore_sky_light_from_heap --- minecraft-server/src/light.rs | 115 +++++++++++++++++++++++++++++++--- minecraft-server/src/map.rs | 14 +++-- 2 files changed, 115 insertions(+), 14 deletions(-) diff --git a/minecraft-server/src/light.rs b/minecraft-server/src/light.rs index 29132fdd..fff73a09 100644 --- a/minecraft-server/src/light.rs +++ b/minecraft-server/src/light.rs @@ -1,5 +1,7 @@ use std::collections::BinaryHeap; +use minecraft_protocol::ids::blocks::Block; + use crate::prelude::*; @@ -99,7 +101,6 @@ impl SkyLight { return Err(()); } - println!("Set region from {} to {} with level {}", from_y, to_y, level); // Get the range of sections to set. let first_section = (from_y.div_euclid(16) + self.zero_chunk_index as i32).max(0) as usize; let first_secion_offset = from_y.rem_euclid(16) as usize; @@ -111,14 +112,11 @@ impl SkyLight { if section != first_section && section != last_section { // Set the whole section self.sky_light_arrays[section].set_with(level); - println!("Set section {}", section); } else { - println!("Set part of section {}", section); // Set the part of the section let first_offset = if section == first_section { first_secion_offset } else { 0 }; let last_offset = if section == last_section { last_section_offset } else { 15 }; for y in first_offset..=last_offset { - println!("Set layer {}", y); self.sky_light_arrays[section].set_layer(y as u8, level)?; } } @@ -134,6 +132,16 @@ impl SkyLight { Ok(()) } + + pub(super) fn get_level(&self, position: BlockPositionInChunkColumn) -> Result { + let section = position.cy() + self.zero_chunk_index as i32; + self.sky_light_arrays[section.max(0) as usize].get(position.in_chunk()) + } + + pub(super) fn set_level(&mut self, position: BlockPositionInChunkColumn, level: u8) -> Result<(), ()> { + let section = position.cy() + self.zero_chunk_index as i32; + self.sky_light_arrays[section.max(0) as usize].set(position.in_chunk(), level) + } } pub(super) struct Light { @@ -156,10 +164,11 @@ impl Light { } } +#[derive(Debug, Clone)] struct HeightBasedPosition { - x: u8, + bx: u8, y: usize, - z: u8, + bz: u8, } impl PartialEq for HeightBasedPosition { @@ -168,6 +177,16 @@ impl PartialEq for HeightBasedPosition { } } +impl From for BlockPositionInChunkColumn { + fn from(val: HeightBasedPosition) -> Self { + BlockPositionInChunkColumn { + bx: val.bx, + y: val.y as i32, + bz: val.bz, + } + } +} + impl std::cmp::Eq for HeightBasedPosition {} impl std::cmp::PartialOrd for HeightBasedPosition { @@ -183,7 +202,7 @@ impl std::cmp::Ord for HeightBasedPosition { } impl ChunkColumn { - pub(super) fn init_light(&mut self) -> Result<(), ()>{ + pub(super) fn init_light(&mut self) -> Result<(), ()> { self.propagate_sky_light_inside()?; Ok(()) } @@ -200,14 +219,77 @@ impl ChunkColumn { // Add all highest blocks to the queue for x in 0..16 { for z in 0..16 { + let position = HeightBasedPosition { + bx: x, + y: self.get_hiest_block_at(&BlockPositionInChunkColumn { bx: x, y: 0, bz: z }) as usize, + bz: z, + }; + to_explore.push(position); } } - while let Some(position) = to_explore.iter().next() { - + self.explore_sky_light_from_heap(&mut to_explore).map_err(|_| error!("Error while updating light")).unwrap(); + + Ok(()) + } + + fn explore_sky_light_from_heap(&mut self, to_explore: &mut BinaryHeap) -> Result<(), ()> { + while let Some(position) = to_explore.pop() { + let mut neighbors = Vec::new(); + let is_inside = self.get_hiest_block_at(&position.clone().into()) > position.y as u16; + let my_level = self.light.sky_light.get_level(position.clone().into())?; + + if position.bx > 0 { + neighbors.push(HeightBasedPosition { bx: position.bx - 1, y: position.y, bz: position.bz }); + } + if position.bx < 15 { + neighbors.push(HeightBasedPosition { bx: position.bx + 1, y: position.y, bz: position.bz }); + } + if position.bz > 0 { + neighbors.push(HeightBasedPosition { bx: position.bx, y: position.y, bz: position.bz - 1 }); + } + if position.bz < 15 { + neighbors.push(HeightBasedPosition { bx: position.bx, y: position.y, bz: position.bz + 1 }); + } + if position.y > 0 { + neighbors.push(HeightBasedPosition { bx: position.bx, y: position.y - 1, bz: position.bz }); + } + if position.y < ((self.light.sky_light.sky_light_arrays.len() - 1) * 16) + 1 { // No block can be higher so no block can affect the light level + neighbors.push(HeightBasedPosition { bx: position.bx, y: position.y + 1, bz: position.bz }); + } + + for neighbor in neighbors { + let neighbor_position: BlockPositionInChunkColumn = neighbor.clone().into(); + let neighbor_level = self.light.sky_light.get_level(neighbor_position.clone())?; + + if Block::from(self.get_block(neighbor_position.clone())).is_transparent() + && (is_inside && neighbor_level < my_level.saturating_sub(1)) + { + to_explore.push(neighbor.clone()); + let new_level = my_level - 1; + self.light.sky_light.set_level(neighbor_position, new_level)?; + } + } } Ok(()) } + + pub(super) fn update_light_at(&mut self, position: BlockPositionInChunkColumn) { + let position1 = HeightBasedPosition { + bx: position.bx, + y: position.y as usize, + bz: position.bz, + }; + let position2 = HeightBasedPosition { + bx: position.bx, + y: position.y as usize + 1, // We want to update the light above the block so we need the + bz: position.bz, + }; + let mut to_explore: BinaryHeap = BinaryHeap::new(); + to_explore.push(position2); + to_explore.push(position1); + self.explore_sky_light_from_heap(&mut to_explore).map_err(|_| error!("Error while updating light")).unwrap(); + } } #[cfg(test)] @@ -288,10 +370,23 @@ mod tests { // Check that the sky light is equal to the light level above the grass and on the top of the world. for x in 0..16 { for z in 0..16 { + assert_eq!(flat_chunk.light.sky_light.sky_light_arrays[0].get(BlockPositionInChunk { bx: x, by: 0, bz: z }).unwrap(), 0); assert_eq!(flat_chunk.light.sky_light.sky_light_arrays[4].get(BlockPositionInChunk { bx: x, by: 0, bz: z }).unwrap(), 15); assert_eq!(flat_chunk.light.sky_light.sky_light_arrays[25].get(BlockPositionInChunk { bx: x, by: 15, bz: z }).unwrap(), 15); } } - // Check that the sky light is under the grass + + // Break the grass block and check that the sky light is correct. + flat_chunk.set_block_for_test(BlockPositionInChunkColumn { bx: 0, y: 15, bz: 0 }, Block::Air.into()); + assert_eq!(flat_chunk.light.sky_light.sky_light_arrays[1].get(BlockPositionInChunk { bx: 0, by: 15, bz: 0 }).unwrap(), 15); + + flat_chunk.set_block_for_test(BlockPositionInChunkColumn { bx: 0, y: 14, bz: 0 }, Block::Air.into()); + assert_eq!(flat_chunk.light.sky_light.sky_light_arrays[1].get(BlockPositionInChunk { bx: 0, by: 14, bz: 0 }).unwrap(), 15); + + flat_chunk.set_block_for_test(BlockPositionInChunkColumn { bx: 0, y: 14, bz: 1 }, Block::Air.into()); + assert_eq!(flat_chunk.light.sky_light.sky_light_arrays[1].get(BlockPositionInChunk { bx: 0, by: 14, bz: 10 }).unwrap(), 0); + assert_eq!(flat_chunk.light.sky_light.sky_light_arrays[1].get(BlockPositionInChunk { bx: 0, by: 14, bz: 1 }).unwrap(), 14); + + } } diff --git a/minecraft-server/src/map.rs b/minecraft-server/src/map.rs index f5ad0311..75728e28 100644 --- a/minecraft-server/src/map.rs +++ b/minecraft-server/src/map.rs @@ -380,7 +380,7 @@ impl ChunkColumn { Self::from(chunks) } - fn get_block(&self, position: BlockPositionInChunkColumn) -> BlockWithState { + pub(super) fn get_block(&self, position: BlockPositionInChunkColumn) -> BlockWithState { fn get_block_inner(s: &ChunkColumn, position: BlockPositionInChunkColumn) -> Option { let cy = position.cy(); let cy_in_vec: usize = cy.saturating_add(4).try_into().ok()?; @@ -391,6 +391,11 @@ impl ChunkColumn { get_block_inner(self, position).unwrap_or(BlockWithState::Air) } + #[cfg(test)] + pub fn set_block_for_test(&mut self, position: BlockPositionInChunkColumn, block: BlockWithState) { + self.set_block(position, block); + } + fn set_block(&mut self, position: BlockPositionInChunkColumn, block: BlockWithState) { fn set_block_innter(s: &mut ChunkColumn, position: BlockPositionInChunkColumn, block: BlockWithState) -> Option<()> { let cy = position.cy(); @@ -418,7 +423,7 @@ impl ChunkColumn { }, _ => {} } - + self.update_light_at(position.clone()); set_block_innter(self, position, block); } } @@ -457,7 +462,7 @@ impl WorldMap { Some(chunk.as_network_chunk().clone()) } - + pub async fn set_block(&self, position: BlockPosition, block: BlockWithState) { async fn inner_get_block(s: &WorldMap, position: BlockPosition, block: BlockWithState) -> Option<()> { let chunk_position = position.chunk(); @@ -467,7 +472,8 @@ impl WorldMap { let mut shard = s.shards[shard].write().await; let chunk_column = shard.get_mut(&chunk_column_position)?; - chunk_column.set_block(position_in_chunk_column, block); + chunk_column.set_block(position_in_chunk_column.clone(), block); + chunk_column.update_light_at(position_in_chunk_column); Some(()) } inner_get_block(self, position, block).await; From d60d6bcd921d73af0656891ae8b838c55d5cbca4 Mon Sep 17 00:00:00 2001 From: DimitriTimoz Date: Mon, 13 Nov 2023 00:57:11 +0100 Subject: [PATCH 20/46] Fix update light from highest block break --- minecraft-server/src/light.rs | 151 +++++++++++++++++++--------------- minecraft-server/src/map.rs | 5 +- 2 files changed, 89 insertions(+), 67 deletions(-) diff --git a/minecraft-server/src/light.rs b/minecraft-server/src/light.rs index fff73a09..92b11652 100644 --- a/minecraft-server/src/light.rs +++ b/minecraft-server/src/light.rs @@ -1,4 +1,4 @@ -use std::collections::BinaryHeap; +use std::{collections::BinaryHeap, ops::AddAssign}; use minecraft_protocol::ids::blocks::Block; @@ -96,18 +96,19 @@ struct SkyLight { impl SkyLight { /// Set the sky light in the given section. - pub fn set_region(&mut self, from_y: i32, to_y: i32, level: u8) -> Result<(), ()> { + pub fn set_region(&mut self, from_y: usize, to_y: usize, level: u8) -> Result<(), ()> { if level > self.level { return Err(()); } // Get the range of sections to set. - let first_section = (from_y.div_euclid(16) + self.zero_chunk_index as i32).max(0) as usize; - let first_secion_offset = from_y.rem_euclid(16) as usize; + let first_section = from_y.div_euclid(16); + let first_secion_offset = from_y.rem_euclid(16); - let last_section = (to_y.div_euclid(16) + self.zero_chunk_index as i32).max(0) as usize; - let last_section_offset = to_y.rem_euclid(16) as usize; + let last_section = to_y.div_euclid(16); + let last_section_offset = to_y.rem_euclid(16); + println!("Setting sky light from {} to {} in sections {} to {}", from_y, to_y, first_section, last_section); for section in first_section..=last_section { if section != first_section && section != last_section { // Set the whole section @@ -133,14 +134,14 @@ impl SkyLight { Ok(()) } - pub(super) fn get_level(&self, position: BlockPositionInChunkColumn) -> Result { - let section = position.cy() + self.zero_chunk_index as i32; - self.sky_light_arrays[section.max(0) as usize].get(position.in_chunk()) + pub(super) fn get_level(&self, position: LightPositionInChunkColumn) -> Result { + let section = position.y.div_euclid(16); + self.sky_light_arrays[section.max(0)].get(position.in_chunk()) } - pub(super) fn set_level(&mut self, position: BlockPositionInChunkColumn, level: u8) -> Result<(), ()> { - let section = position.cy() + self.zero_chunk_index as i32; - self.sky_light_arrays[section.max(0) as usize].set(position.in_chunk(), level) + pub(super) fn set_level(&mut self, position: LightPositionInChunkColumn, level: u8) -> Result<(), ()> { + let section = position.y.div_euclid(16); + self.sky_light_arrays[section.max(0)].set(position.in_chunk(), level) } } @@ -148,7 +149,6 @@ pub(super) struct Light { sky_light: SkyLight, } - impl Light { pub fn new() -> Self { // TODO: Make this configurable with the world. @@ -165,37 +165,63 @@ impl Light { } #[derive(Debug, Clone)] -struct HeightBasedPosition { +struct LightPositionInChunkColumn { bx: u8, y: usize, bz: u8, } -impl PartialEq for HeightBasedPosition { +impl LightPositionInChunkColumn { + pub fn in_chunk(&self) -> BlockPositionInChunk { + BlockPositionInChunk { + bx: self.bx, + by: self.y.rem_euclid(16) as u8, + bz: self.bz, + } + } +} + +impl PartialEq for LightPositionInChunkColumn { fn eq(&self, other: &Self) -> bool { self.y == other.y } } -impl From for BlockPositionInChunkColumn { - fn from(val: HeightBasedPosition) -> Self { +impl From for BlockPositionInChunkColumn { + fn from(val: LightPositionInChunkColumn) -> Self { BlockPositionInChunkColumn { bx: val.bx, - y: val.y as i32, + y: val.y as i32 - 64 - 16, // TODO: Use the world config bz: val.bz, } } } -impl std::cmp::Eq for HeightBasedPosition {} +impl From for LightPositionInChunkColumn { + fn from(val: BlockPositionInChunkColumn) -> Self { + LightPositionInChunkColumn { + bx: val.bx, + y: (val.y + 64 + 16) as usize, //-TODO: Use the world config + bz: val.bz, + } + } +} -impl std::cmp::PartialOrd for HeightBasedPosition { +impl AddAssign for LightPositionInChunkColumn { + fn add_assign(&mut self, rhs: usize) { + self.y += rhs; + } +} + +impl std::cmp::Eq for LightPositionInChunkColumn {} + +impl std::cmp::PartialOrd for LightPositionInChunkColumn { fn partial_cmp(&self, other: &Self) -> Option { Some(self.y.cmp(&other.y)) } } -impl std::cmp::Ord for HeightBasedPosition { +impl std::cmp::Ord for LightPositionInChunkColumn { fn cmp(&self, other: &Self) -> std::cmp::Ordering { self.y.cmp(&other.y) } @@ -211,17 +237,17 @@ impl ChunkColumn { // Set all highest blocks to the highest block let highest_blocks = self.get_highest_block(); - let max_y = (self.light.sky_light.sky_light_arrays.len() as i32 - self.light.sky_light.zero_chunk_index as i32) * 16 - 1; - self.light.sky_light.set_region(highest_blocks as i32 - 64, max_y, self.light.sky_light.level)?; - let mut to_explore: BinaryHeap = BinaryHeap::new(); + let max_y = self.light.sky_light.sky_light_arrays.len() * 16 - 1; + self.light.sky_light.set_region(highest_blocks as usize + 16, max_y, self.light.sky_light.level)?; + let mut to_explore: BinaryHeap = BinaryHeap::new(); // Add all highest blocks to the queue for x in 0..16 { for z in 0..16 { - let position = HeightBasedPosition { + let position = LightPositionInChunkColumn { bx: x, - y: self.get_hiest_block_at(&BlockPositionInChunkColumn { bx: x, y: 0, bz: z }) as usize, + y: self.get_hiest_block_at(&BlockPositionInChunkColumn { bx: x, y: 0, bz: z }) as usize + 16 + 1, bz: z, }; to_explore.push(position); @@ -233,41 +259,44 @@ impl ChunkColumn { Ok(()) } - fn explore_sky_light_from_heap(&mut self, to_explore: &mut BinaryHeap) -> Result<(), ()> { + fn explore_sky_light_from_heap(&mut self, to_explore: &mut BinaryHeap) -> Result<(), ()> { while let Some(position) = to_explore.pop() { + println!("Exploring {:?}", position); let mut neighbors = Vec::new(); let is_inside = self.get_hiest_block_at(&position.clone().into()) > position.y as u16; - let my_level = self.light.sky_light.get_level(position.clone().into())?; - + let my_level = self.light.sky_light.get_level(position.clone())?; if position.bx > 0 { - neighbors.push(HeightBasedPosition { bx: position.bx - 1, y: position.y, bz: position.bz }); + neighbors.push(LightPositionInChunkColumn { bx: position.bx - 1, y: position.y, bz: position.bz }); } if position.bx < 15 { - neighbors.push(HeightBasedPosition { bx: position.bx + 1, y: position.y, bz: position.bz }); + neighbors.push(LightPositionInChunkColumn { bx: position.bx + 1, y: position.y, bz: position.bz }); } if position.bz > 0 { - neighbors.push(HeightBasedPosition { bx: position.bx, y: position.y, bz: position.bz - 1 }); + neighbors.push(LightPositionInChunkColumn { bx: position.bx, y: position.y, bz: position.bz - 1 }); } if position.bz < 15 { - neighbors.push(HeightBasedPosition { bx: position.bx, y: position.y, bz: position.bz + 1 }); + neighbors.push(LightPositionInChunkColumn { bx: position.bx, y: position.y, bz: position.bz + 1 }); } if position.y > 0 { - neighbors.push(HeightBasedPosition { bx: position.bx, y: position.y - 1, bz: position.bz }); + neighbors.push(LightPositionInChunkColumn { bx: position.bx, y: position.y - 1, bz: position.bz }); } if position.y < ((self.light.sky_light.sky_light_arrays.len() - 1) * 16) + 1 { // No block can be higher so no block can affect the light level - neighbors.push(HeightBasedPosition { bx: position.bx, y: position.y + 1, bz: position.bz }); + neighbors.push(LightPositionInChunkColumn { bx: position.bx, y: position.y + 1, bz: position.bz }); } for neighbor in neighbors { - let neighbor_position: BlockPositionInChunkColumn = neighbor.clone().into(); - let neighbor_level = self.light.sky_light.get_level(neighbor_position.clone())?; + let neighbor_level = self.light.sky_light.get_level(neighbor.clone())?; - if Block::from(self.get_block(neighbor_position.clone())).is_transparent() - && (is_inside && neighbor_level < my_level.saturating_sub(1)) + println!("Neighbor: {:?}, level: {}", neighbor, neighbor_level); + let block = Block::from(self.get_block(neighbor.clone().into())); + println!("Is transparent {}", block.is_transparent()); + if block.is_transparent() + && (neighbor_level < my_level.saturating_sub(1)) { + println!("Updating light at {:?} from {:?}", neighbor, position); to_explore.push(neighbor.clone()); - let new_level = my_level - 1; - self.light.sky_light.set_level(neighbor_position, new_level)?; + let new_level = if is_inside { my_level - 1 } else { self.light.sky_light.level }; + self.light.sky_light.set_level(neighbor, new_level)?; } } } @@ -275,19 +304,10 @@ impl ChunkColumn { } pub(super) fn update_light_at(&mut self, position: BlockPositionInChunkColumn) { - let position1 = HeightBasedPosition { - bx: position.bx, - y: position.y as usize, - bz: position.bz, - }; - let position2 = HeightBasedPosition { - bx: position.bx, - y: position.y as usize + 1, // We want to update the light above the block so we need the - bz: position.bz, - }; - let mut to_explore: BinaryHeap = BinaryHeap::new(); - to_explore.push(position2); - to_explore.push(position1); + let mut start_position: LightPositionInChunkColumn = position.into(); + start_position += 1; + let mut to_explore: BinaryHeap = BinaryHeap::new(); + to_explore.push(start_position ); self.explore_sky_light_from_heap(&mut to_explore).map_err(|_| error!("Error while updating light")).unwrap(); } } @@ -350,17 +370,17 @@ mod tests { zero_chunk_index: 4, // We start at y=-64, and we have a chunk under that. }; - sky_light.set_region(-1, 16, 15).unwrap(); + sky_light.set_region(1, 33, 15).unwrap(); // Test in - assert_eq!(sky_light.sky_light_arrays[5].get(BlockPositionInChunk { bx: 0, by: 0, bz: 7 }).unwrap(), 15); - assert_eq!(sky_light.sky_light_arrays[4].get(BlockPositionInChunk { bx: 1, by: 2, bz: 8 }).unwrap(), 15); - assert_eq!(sky_light.sky_light_arrays[4].get(BlockPositionInChunk { bx: 3, by: 0, bz: 0 }).unwrap(), 15); + assert_eq!(sky_light.sky_light_arrays[0].get(BlockPositionInChunk { bx: 0, by: 1, bz: 7 }).unwrap(), 15); + assert_eq!(sky_light.sky_light_arrays[1].get(BlockPositionInChunk { bx: 1, by: 15, bz: 8 }).unwrap(), 15); + assert_eq!(sky_light.sky_light_arrays[2].get(BlockPositionInChunk { bx: 3, by: 0, bz: 0 }).unwrap(), 15); // Test out - assert_eq!(sky_light.sky_light_arrays[5].get(BlockPositionInChunk { bx: 4, by: 1, bz: 2 }).unwrap(), 0); + assert_eq!(sky_light.sky_light_arrays[0].get(BlockPositionInChunk { bx: 4, by: 0, bz: 2 }).unwrap(), 0); assert_eq!(sky_light.sky_light_arrays[3].get(BlockPositionInChunk { bx: 0, by: 14, bz: 9 }).unwrap(), 0); - assert_eq!(sky_light.sky_light_arrays[0].get(BlockPositionInChunk { bx: 9, by: 0, bz: 10 }).unwrap(), 0); + assert_eq!(sky_light.sky_light_arrays[4].get(BlockPositionInChunk { bx: 9, by: 0, bz: 10 }).unwrap(), 0); } #[test] @@ -377,16 +397,17 @@ mod tests { } // Break the grass block and check that the sky light is correct. - flat_chunk.set_block_for_test(BlockPositionInChunkColumn { bx: 0, y: 15, bz: 0 }, Block::Air.into()); + assert_eq!(flat_chunk.light.sky_light.sky_light_arrays[1].get(BlockPositionInChunk { bx: 0, by: 15, bz: 0 }).unwrap(), 0); + flat_chunk.set_block_for_test(BlockPositionInChunkColumn { bx: 0, y: -49, bz: 0 }, Block::Air.into()); assert_eq!(flat_chunk.light.sky_light.sky_light_arrays[1].get(BlockPositionInChunk { bx: 0, by: 15, bz: 0 }).unwrap(), 15); - flat_chunk.set_block_for_test(BlockPositionInChunkColumn { bx: 0, y: 14, bz: 0 }, Block::Air.into()); + assert_eq!(flat_chunk.light.sky_light.sky_light_arrays[1].get(BlockPositionInChunk { bx: 0, by: 14, bz: 0 }).unwrap(), 0); + flat_chunk.set_block_for_test(BlockPositionInChunkColumn { bx: 0, y: -50, bz: 0 }, Block::Air.into()); assert_eq!(flat_chunk.light.sky_light.sky_light_arrays[1].get(BlockPositionInChunk { bx: 0, by: 14, bz: 0 }).unwrap(), 15); - flat_chunk.set_block_for_test(BlockPositionInChunkColumn { bx: 0, y: 14, bz: 1 }, Block::Air.into()); - assert_eq!(flat_chunk.light.sky_light.sky_light_arrays[1].get(BlockPositionInChunk { bx: 0, by: 14, bz: 10 }).unwrap(), 0); + flat_chunk.set_block_for_test(BlockPositionInChunkColumn { bx: 0, y: -50, bz: 1 }, Block::Air.into()); assert_eq!(flat_chunk.light.sky_light.sky_light_arrays[1].get(BlockPositionInChunk { bx: 0, by: 14, bz: 1 }).unwrap(), 14); - + assert_eq!(flat_chunk.light.sky_light.sky_light_arrays[1].get(BlockPositionInChunk { bx: 0, by: 0, bz: 10 }).unwrap(), 0); } } diff --git a/minecraft-server/src/map.rs b/minecraft-server/src/map.rs index 75728e28..ac5b6cc7 100644 --- a/minecraft-server/src/map.rs +++ b/minecraft-server/src/map.rs @@ -393,6 +393,7 @@ impl ChunkColumn { #[cfg(test)] pub fn set_block_for_test(&mut self, position: BlockPositionInChunkColumn, block: BlockWithState) { + println!("set block at {:?}", position); self.set_block(position, block); } @@ -423,8 +424,8 @@ impl ChunkColumn { }, _ => {} } - self.update_light_at(position.clone()); - set_block_innter(self, position, block); + set_block_innter(self, position.clone(), block); + self.update_light_at(position); } } From fa8484f648c9b10bf3c5e6c467e19c4dc99cc62a Mon Sep 17 00:00:00 2001 From: DimitriTimoz Date: Mon, 13 Nov 2023 22:17:12 +0100 Subject: [PATCH 21/46] fix heightMap --- minecraft-server/src/map.rs | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/minecraft-server/src/map.rs b/minecraft-server/src/map.rs index ac5b6cc7..95c754f9 100644 --- a/minecraft-server/src/map.rs +++ b/minecraft-server/src/map.rs @@ -208,7 +208,8 @@ impl HeightMap { /// Set the height of the highest block at the given position. pub fn set(&mut self, position: &BlockPositionInChunkColumn, height: u32) { let (x, z) = (position.bx, position.bz); - + println!("set height at {:?}", position); + println!("height: {:?}", height); // Check if the height is higher than the current max height. if let Some(max_height) = self.max_height { if height < max_height { // Calculate the new base for the data. @@ -393,7 +394,6 @@ impl ChunkColumn { #[cfg(test)] pub fn set_block_for_test(&mut self, position: BlockPositionInChunkColumn, block: BlockWithState) { - println!("set block at {:?}", position); self.set_block(position, block); } @@ -403,28 +403,33 @@ impl ChunkColumn { let cy_in_vec: usize = cy.saturating_add(4).try_into().ok()?; let position = position.in_chunk(); let chunk = s.chunks.get_mut(cy_in_vec)?; - chunk.set_block(position, block); + chunk.set_block(position, block.clone()); Some(()) } + set_block_innter(self, position.clone(), block.clone()); let last_height = self.heightmap.get(&position); - let filter_sunlight = Block::from(block.clone()).is_transparent(); // TODO: check if the block is transparent + let not_filter_sunlight = Block::from(block.clone()).is_transparent(); // TODO: check if the block is transparent // Get the height of the placed block - let block_height = (position.y - Self::MIN_Y).max(0) as u16; - + let block_height = (position.y - Self::MIN_Y + 1).max(0) as u16; + println!("set {:?} at {:?} ", block, position); + println!("block height: {:?}", block_height); + println!("last height: {:?}", last_height); + println!("let pass sunlight: {:?}", not_filter_sunlight); match block_height.cmp(&last_height) { - Ordering::Greater if !filter_sunlight => { + Ordering::Greater if !not_filter_sunlight => { + println!("greater"); self.heightmap.set(&position, block_height.into()); }, - Ordering::Equal if filter_sunlight => { + Ordering::Equal if not_filter_sunlight => { // Downward propagation + println!("equal"); let new_height = self.get_higher_skylight_filter_block(&position, last_height).into(); self.heightmap.set(&position, new_height); }, _ => {} } - set_block_innter(self, position.clone(), block); self.update_light_at(position); } } @@ -682,11 +687,11 @@ mod tests { // Now check that the heightmap is correct after setting a block flat_column.set_block(BlockPositionInChunkColumn { bx: 0, y: 10, bz: 0 }, BlockWithState::GrassBlock { snowy: false }); - assert_eq!(flat_column.heightmap.get(&BlockPositionInChunkColumn { bx: 0, y: 0, bz: 0 }), 74); + assert_eq!(flat_column.heightmap.get(&BlockPositionInChunkColumn { bx: 0, y: 0, bz: 0 }), 75); // Check that the heightmap is correct after setting a block to air under the highest block flat_column.set_block(BlockPositionInChunkColumn { bx: 0, y: 8, bz: 0 }, BlockWithState::Air); - assert_eq!(flat_column.heightmap.get(&BlockPositionInChunkColumn { bx: 0, y: 0, bz: 0 }), 74); + assert_eq!(flat_column.heightmap.get(&BlockPositionInChunkColumn { bx: 0, y: 0, bz: 0 }), 75); // Check that the heightmap is correct after setting the highest block to air flat_column.set_block(BlockPositionInChunkColumn { bx: 0, y: 10, bz: 0 }, BlockWithState::Air); From 1e17721d4b07a878b2b711e1d1b46929ee0d3178 Mon Sep 17 00:00:00 2001 From: DimitriTimoz Date: Mon, 13 Nov 2023 22:18:36 +0100 Subject: [PATCH 22/46] Flat world light update --- minecraft-server/src/light.rs | 35 +++++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/minecraft-server/src/light.rs b/minecraft-server/src/light.rs index 92b11652..5ed79c1a 100644 --- a/minecraft-server/src/light.rs +++ b/minecraft-server/src/light.rs @@ -2,7 +2,7 @@ use std::{collections::BinaryHeap, ops::AddAssign}; use minecraft_protocol::ids::blocks::Block; -use crate::prelude::*; +use crate::{prelude::*, position}; #[derive(Debug, Clone)] @@ -166,9 +166,9 @@ impl Light { #[derive(Debug, Clone)] struct LightPositionInChunkColumn { - bx: u8, - y: usize, - bz: u8, + pub bx: u8, + pub y: usize, + pub bz: u8, } impl LightPositionInChunkColumn { @@ -261,7 +261,6 @@ impl ChunkColumn { fn explore_sky_light_from_heap(&mut self, to_explore: &mut BinaryHeap) -> Result<(), ()> { while let Some(position) = to_explore.pop() { - println!("Exploring {:?}", position); let mut neighbors = Vec::new(); let is_inside = self.get_hiest_block_at(&position.clone().into()) > position.y as u16; let my_level = self.light.sky_light.get_level(position.clone())?; @@ -287,13 +286,12 @@ impl ChunkColumn { for neighbor in neighbors { let neighbor_level = self.light.sky_light.get_level(neighbor.clone())?; - println!("Neighbor: {:?}, level: {}", neighbor, neighbor_level); let block = Block::from(self.get_block(neighbor.clone().into())); - println!("Is transparent {}", block.is_transparent()); if block.is_transparent() && (neighbor_level < my_level.saturating_sub(1)) { - println!("Updating light at {:?} from {:?}", neighbor, position); + let highest_block = self.get_hiest_block_at(&neighbor.clone().into()) + 16; + let is_inside = highest_block > neighbor.y as u16 + 1; to_explore.push(neighbor.clone()); let new_level = if is_inside { my_level - 1 } else { self.light.sky_light.level }; self.light.sky_light.set_level(neighbor, new_level)?; @@ -304,10 +302,17 @@ impl ChunkColumn { } pub(super) fn update_light_at(&mut self, position: BlockPositionInChunkColumn) { - let mut start_position: LightPositionInChunkColumn = position.into(); - start_position += 1; - let mut to_explore: BinaryHeap = BinaryHeap::new(); - to_explore.push(start_position ); + let position = LightPositionInChunkColumn::from(position); + let (bx, y, bz) = (position.bx, position.y, position.bz); + + let mut to_explore: BinaryHeap = BinaryHeap::from(vec![ + LightPositionInChunkColumn { bx, y: y + 1, bz }, + LightPositionInChunkColumn { bx: (bx + 1) % 16, y, bz }, + LightPositionInChunkColumn { bx: bx.saturating_sub(1), y, bz }, + LightPositionInChunkColumn { bx, y, bz: (bz + 1) % 16 }, + LightPositionInChunkColumn { bx, y, bz: bz.saturating_sub(1)}, + ]); + self.explore_sky_light_from_heap(&mut to_explore).map_err(|_| error!("Error while updating light")).unwrap(); } } @@ -409,5 +414,11 @@ mod tests { assert_eq!(flat_chunk.light.sky_light.sky_light_arrays[1].get(BlockPositionInChunk { bx: 0, by: 14, bz: 1 }).unwrap(), 14); assert_eq!(flat_chunk.light.sky_light.sky_light_arrays[1].get(BlockPositionInChunk { bx: 0, by: 0, bz: 10 }).unwrap(), 0); + flat_chunk.set_block_for_test(BlockPositionInChunkColumn { bx: 0, y: -50, bz: 2 }, Block::Air.into()); + assert_eq!(flat_chunk.light.sky_light.sky_light_arrays[1].get(BlockPositionInChunk { bx: 0, by: 14, bz: 2 }).unwrap(), 13); + + flat_chunk.set_block_for_test(BlockPositionInChunkColumn { bx: 0, y: -51, bz: 2 }, Block::Air.into()); + assert_eq!(flat_chunk.light.sky_light.sky_light_arrays[1].get(BlockPositionInChunk { bx: 0, by: 13, bz: 2 }).unwrap(), 12); + } } From adfd07fff56d5a732146f2925d4c4b939a11f4ab Mon Sep 17 00:00:00 2001 From: DimitriTimoz Date: Mon, 13 Nov 2023 23:23:36 +0100 Subject: [PATCH 23/46] get network heightMap --- minecraft-server/src/light.rs | 12 ++++++--- minecraft-server/src/map.rs | 30 ++++++++++++++------- minecraft-server/src/player_handler/play.rs | 10 ++++--- minecraft-server/src/world.rs | 4 +++ 4 files changed, 39 insertions(+), 17 deletions(-) diff --git a/minecraft-server/src/light.rs b/minecraft-server/src/light.rs index 5ed79c1a..c3366e2e 100644 --- a/minecraft-server/src/light.rs +++ b/minecraft-server/src/light.rs @@ -95,6 +95,9 @@ struct SkyLight { } impl SkyLight { + pub fn to_packet(&self) -> () { + + } /// Set the sky light in the given section. pub fn set_region(&mut self, from_y: usize, to_y: usize, level: u8) -> Result<(), ()> { if level > self.level { @@ -247,7 +250,7 @@ impl ChunkColumn { for z in 0..16 { let position = LightPositionInChunkColumn { bx: x, - y: self.get_hiest_block_at(&BlockPositionInChunkColumn { bx: x, y: 0, bz: z }) as usize + 16 + 1, + y: self.get_highest_block_at(&BlockPositionInChunkColumn { bx: x, y: 0, bz: z }) as usize + 16 + 1, bz: z, }; to_explore.push(position); @@ -260,9 +263,9 @@ impl ChunkColumn { } fn explore_sky_light_from_heap(&mut self, to_explore: &mut BinaryHeap) -> Result<(), ()> { + error!("start loop"); while let Some(position) = to_explore.pop() { let mut neighbors = Vec::new(); - let is_inside = self.get_hiest_block_at(&position.clone().into()) > position.y as u16; let my_level = self.light.sky_light.get_level(position.clone())?; if position.bx > 0 { neighbors.push(LightPositionInChunkColumn { bx: position.bx - 1, y: position.y, bz: position.bz }); @@ -285,12 +288,12 @@ impl ChunkColumn { for neighbor in neighbors { let neighbor_level = self.light.sky_light.get_level(neighbor.clone())?; - + let block = Block::from(self.get_block(neighbor.clone().into())); if block.is_transparent() && (neighbor_level < my_level.saturating_sub(1)) { - let highest_block = self.get_hiest_block_at(&neighbor.clone().into()) + 16; + let highest_block = self.get_highest_block_at(&neighbor.clone().into()) + 16; let is_inside = highest_block > neighbor.y as u16 + 1; to_explore.push(neighbor.clone()); let new_level = if is_inside { my_level - 1 } else { self.light.sky_light.level }; @@ -298,6 +301,7 @@ impl ChunkColumn { } } } + error!("end loop"); Ok(()) } diff --git a/minecraft-server/src/map.rs b/minecraft-server/src/map.rs index 95c754f9..524500ad 100644 --- a/minecraft-server/src/map.rs +++ b/minecraft-server/src/map.rs @@ -189,6 +189,18 @@ impl HeightMap { } } + pub fn to_tag(&self) -> NbtTag { + NbtTag::Compound( + HashMap::from_iter( + vec![ + (String::from("MOTION_BLOCKING"), NbtTag::LongArray(unsafe { + std::mem::transmute::, Vec>(self.data.clone()) + })), + ] + ) + ) + } + /// Update the current base of the heightmap. fn new_base(&mut self, new_base: u8) { assert!(new_base <= 9, "base must be <= 9 because the max height is 320 + 64"); @@ -208,8 +220,6 @@ impl HeightMap { /// Set the height of the highest block at the given position. pub fn set(&mut self, position: &BlockPositionInChunkColumn, height: u32) { let (x, z) = (position.bx, position.bz); - println!("set height at {:?}", position); - println!("height: {:?}", height); // Check if the height is higher than the current max height. if let Some(max_height) = self.max_height { if height < max_height { // Calculate the new base for the data. @@ -341,7 +351,7 @@ impl ChunkColumn { self.heightmap.max_height.unwrap_or(0) } - pub(super) fn get_hiest_block_at(&self, position: &BlockPositionInChunkColumn) -> u16 { + pub(super) fn get_highest_block_at(&self, position: &BlockPositionInChunkColumn) -> u16 { self.heightmap.get(position) } @@ -413,18 +423,12 @@ impl ChunkColumn { // Get the height of the placed block let block_height = (position.y - Self::MIN_Y + 1).max(0) as u16; - println!("set {:?} at {:?} ", block, position); - println!("block height: {:?}", block_height); - println!("last height: {:?}", last_height); - println!("let pass sunlight: {:?}", not_filter_sunlight); match block_height.cmp(&last_height) { Ordering::Greater if !not_filter_sunlight => { - println!("greater"); self.heightmap.set(&position, block_height.into()); }, Ordering::Equal if not_filter_sunlight => { // Downward propagation - println!("equal"); let new_height = self.get_higher_skylight_filter_block(&position, last_height).into(); self.heightmap.set(&position, new_height); }, @@ -468,6 +472,14 @@ impl WorldMap { Some(chunk.as_network_chunk().clone()) } + + pub async fn get_network_heightmap(&self, position: ChunkColumnPosition) -> Option { + let shard = position.shard(self.shard_count); + let shard = self.shards[shard].read().await; + let chunk_column = shard.get(&position)?; + + Some(chunk_column.heightmap.to_tag()) + } pub async fn set_block(&self, position: BlockPosition, block: BlockWithState) { async fn inner_get_block(s: &WorldMap, position: BlockPosition, block: BlockWithState) -> Option<()> { diff --git a/minecraft-server/src/player_handler/play.rs b/minecraft-server/src/player_handler/play.rs index 4fc29205..de21aaf0 100644 --- a/minecraft-server/src/player_handler/play.rs +++ b/minecraft-server/src/player_handler/play.rs @@ -71,11 +71,13 @@ impl PlayerHandler { self.world.update_loaded_chunks(self.info.uuid, loaded_chunks_after.clone()).await; // Send the chunks to the client - let mut heightmaps = HashMap::new(); - heightmaps.insert(String::from("MOTION_BLOCKING"), NbtTag::LongArray(vec![0; 37])); - let heightmaps = NbtTag::Compound(heightmaps); for newly_loaded_chunk in newly_loaded_chunks { let mut column = Vec::new(); + let heightmaps = self.world.get_network_heightmap(newly_loaded_chunk.clone()).await.unwrap_or_else(|| { + error!("Chunk not loaded: {newly_loaded_chunk:?}"); + NbtTag::Compound(HashMap::new()) // TODO hard error + }); + for cy in -4..20 { let chunk = self.world.get_network_chunk(newly_loaded_chunk.chunk(cy)).await.unwrap_or_else(|| { error!("Chunk not loaded: {newly_loaded_chunk:?}"); @@ -92,7 +94,7 @@ impl PlayerHandler { value: ChunkData { chunk_x: newly_loaded_chunk.cx, chunk_z: newly_loaded_chunk.cz, - heightmaps: heightmaps.clone(), + heightmaps, data: Array::from(serialized.clone()), block_entities: Array::default(), sky_light_mask: Array::default(), diff --git a/minecraft-server/src/world.rs b/minecraft-server/src/world.rs index ab608c20..8f8b69dc 100644 --- a/minecraft-server/src/world.rs +++ b/minecraft-server/src/world.rs @@ -69,6 +69,10 @@ impl World { self.map.get_network_chunk(position).await } + pub async fn get_network_heightmap(&self, position: ChunkColumnPosition) -> Option { + self.map.get_network_heightmap(position).await + } + pub async fn set_block(&self, position: BlockPosition, block: BlockWithState) { self.map.set_block(position.clone(), block.clone()).await; self.notify(&position.chunk_column(), WorldChange::BlockChange(position, block)).await; From 9b550c47d93e1e1c63ce7bf0da5ca2bd2b29dadb Mon Sep 17 00:00:00 2001 From: DimitriTimoz Date: Mon, 13 Nov 2023 23:40:07 +0100 Subject: [PATCH 24/46] Impl default for Slot --- minecraft-protocol/build/items.rs | 7 +++++++ minecraft-protocol/src/components/slots.rs | 2 +- minecraft-protocol/src/nbt/mod.rs | 6 ++++++ minecraft-server/src/main.rs | 1 - minecraft-server/src/{ => world}/light.rs | 4 ++-- minecraft-server/src/world/map.rs | 3 ++- minecraft-server/src/world/mod.rs | 3 ++- 7 files changed, 20 insertions(+), 6 deletions(-) rename minecraft-server/src/{ => world}/light.rs (99%) diff --git a/minecraft-protocol/build/items.rs b/minecraft-protocol/build/items.rs index b78bef0c..515f418d 100644 --- a/minecraft-protocol/build/items.rs +++ b/minecraft-protocol/build/items.rs @@ -60,6 +60,13 @@ pub enum Item {{ {variants} }} +impl Default for Item {{ + #[inline] + fn default() -> Self {{ + Item::Air + }} +}} + impl Item {{ #[inline] pub fn from_id(id: u32) -> Option {{ diff --git a/minecraft-protocol/src/components/slots.rs b/minecraft-protocol/src/components/slots.rs index abdaa336..38b68f34 100644 --- a/minecraft-protocol/src/components/slots.rs +++ b/minecraft-protocol/src/components/slots.rs @@ -9,7 +9,7 @@ pub struct Slot { } #[cfg_attr(test, derive(PartialEq))] -#[derive(Debug, Clone, MinecraftPacketPart)] +#[derive(Debug, Clone, Default, MinecraftPacketPart)] pub struct SlotItem { /// The [item](crate::ids::items::Item). /// Item IDs are distinct from [block IDs](crate::ids::blocks::Block); see [crate::ids] for more information. diff --git a/minecraft-protocol/src/nbt/mod.rs b/minecraft-protocol/src/nbt/mod.rs index 1bad2873..0df29168 100644 --- a/minecraft-protocol/src/nbt/mod.rs +++ b/minecraft-protocol/src/nbt/mod.rs @@ -25,6 +25,12 @@ pub enum NbtTag { RootCompound(String, HashMap), } +impl Default for NbtTag { + fn default() -> Self { + NbtTag::Null + } +} + impl NbtTag { #[inline] pub fn is_null(&self) -> bool { diff --git a/minecraft-server/src/main.rs b/minecraft-server/src/main.rs index d76cf242..4f554561 100644 --- a/minecraft-server/src/main.rs +++ b/minecraft-server/src/main.rs @@ -5,7 +5,6 @@ mod server_behavior; mod prelude; mod ecs; mod world; -mod light; use crate::prelude::*; diff --git a/minecraft-server/src/light.rs b/minecraft-server/src/world/light.rs similarity index 99% rename from minecraft-server/src/light.rs rename to minecraft-server/src/world/light.rs index c3366e2e..f5d3483c 100644 --- a/minecraft-server/src/light.rs +++ b/minecraft-server/src/world/light.rs @@ -2,8 +2,8 @@ use std::{collections::BinaryHeap, ops::AddAssign}; use minecraft_protocol::ids::blocks::Block; -use crate::{prelude::*, position}; - +use crate::prelude::*; +use super::*; #[derive(Debug, Clone)] struct SectionLightData(Vec); diff --git a/minecraft-server/src/world/map.rs b/minecraft-server/src/world/map.rs index 8de25d02..92bec881 100644 --- a/minecraft-server/src/world/map.rs +++ b/minecraft-server/src/world/map.rs @@ -1,7 +1,8 @@ use std::{collections::HashMap, cmp::Ordering}; use minecraft_protocol::{components::chunk::{PalettedData, self}, ids::blocks::Block}; use tokio::sync::RwLock; -use crate::{prelude::*, light::Light}; +use crate::prelude::*; +use super::light::Light; pub struct WorldMap { /// The map is divided in shards. diff --git a/minecraft-server/src/world/mod.rs b/minecraft-server/src/world/mod.rs index 7e7d1eaa..b9767772 100644 --- a/minecraft-server/src/world/mod.rs +++ b/minecraft-server/src/world/mod.rs @@ -6,7 +6,8 @@ mod loading_manager; use loading_manager::*; mod map; use map::*; - +mod light; +use light::*; /// World is the union of the map and entities. /// World handles loaded chunks and entities. /// It is responsible for notifying players of changes in the world. From ddbe987317a7fbb87a43418468ba77a38bae2fd0 Mon Sep 17 00:00:00 2001 From: DimitriTimoz Date: Tue, 14 Nov 2023 00:27:08 +0100 Subject: [PATCH 25/46] Refactor chunk loading and sending code --- .../src/player_handler/handshake.rs | 39 +++--------------- minecraft-server/src/player_handler/play.rs | 40 ++++--------------- minecraft-server/src/prelude.rs | 2 +- minecraft-server/src/world/map.rs | 34 +++++++++++----- minecraft-server/src/world/mod.rs | 4 +- 5 files changed, 41 insertions(+), 78 deletions(-) diff --git a/minecraft-server/src/player_handler/handshake.rs b/minecraft-server/src/player_handler/handshake.rs index 086869a6..1ec59b07 100644 --- a/minecraft-server/src/player_handler/handshake.rs +++ b/minecraft-server/src/player_handler/handshake.rs @@ -314,42 +314,15 @@ pub async fn handshake(stream: &mut TcpStream, logged_in_player_info: LoggedInPl } } world.update_loaded_chunks(logged_in_player_info.uuid, loaded_chunks).await; - - let mut heightmaps = HashMap::new(); - heightmaps.insert(String::from("MOTION_BLOCKING"), NbtTag::LongArray(vec![0; 37])); - let heightmaps = NbtTag::Compound(heightmaps); for cx in -3..=3 { for cz in -3..=3 { - let mut column = Vec::new(); - for cy in -4..20 { - let chunk = world.get_network_chunk(ChunkPosition { cx, cy, cz }).await.unwrap_or_else(|| { - error!("Chunk not loaded: {cx} {cy} {cz}"); - NetworkChunk { // TODO hard error - block_count: 0, - blocks: PalettedData::Single { value: 0 }, - biomes: PalettedData::Single { value: 4 }, - } - }); - column.push(chunk); - } - let serialized: Vec = NetworkChunk::into_data(column).unwrap(); - let chunk_data = PlayClientbound::ChunkData { - value: ChunkData { - chunk_x: cx, - chunk_z: cz, - heightmaps: heightmaps.clone(), - data: Array::from(serialized.clone()), - block_entities: Array::default(), - sky_light_mask: Array::default(), - block_light_mask: Array::default(), - empty_sky_light_mask: Array::default(), - empty_block_light_mask: Array::default(), - sky_light: Array::default(), - block_light: Array::default(), - } - }; - send_packet(stream, chunk_data).await; + let chunk_column = world.get_network_chunk_column_data(ChunkColumnPosition { cx, cz }).await.unwrap_or_else(|| { + error!("Chunk not loaded: {cx} {cz}"); + panic!("Chunk not loaded: {cx} {cz}"); + }); + + send_packet_raw(stream, &chunk_column).await; } } debug!("ChunkData sent"); diff --git a/minecraft-server/src/player_handler/play.rs b/minecraft-server/src/player_handler/play.rs index dce0f678..459addf9 100644 --- a/minecraft-server/src/player_handler/play.rs +++ b/minecraft-server/src/player_handler/play.rs @@ -21,6 +21,10 @@ impl PlayerHandler { self.packet_sender.send(packet).await.unwrap(); } + async fn send_packet_raw(&mut self, packet: &[u8]) { + self.packet_sender.send(packet.to_vec()).await.unwrap(); + } + async fn on_server_message(&mut self, message: ServerMessage) { use ServerMessage::*; match message { @@ -72,40 +76,12 @@ impl PlayerHandler { // Send the chunks to the client for newly_loaded_chunk in newly_loaded_chunks { - let mut column = Vec::new(); - let heightmaps = self.world.get_network_heightmap(newly_loaded_chunk.clone()).await.unwrap_or_else(|| { + let chunk_column_data = self.world.get_network_chunk_column_data(newly_loaded_chunk.clone()).await.unwrap_or_else(|| { error!("Chunk not loaded: {newly_loaded_chunk:?}"); - NbtTag::Compound(HashMap::new()) // TODO hard error + panic!("Chunk not loaded: {newly_loaded_chunk:?}"); }); - - for cy in -4..20 { - let chunk = self.world.get_network_chunk(newly_loaded_chunk.chunk(cy)).await.unwrap_or_else(|| { - error!("Chunk not loaded: {newly_loaded_chunk:?}"); - NetworkChunk { // TODO hard error - block_count: 0, - blocks: PalettedData::Single { value: 0 }, - biomes: PalettedData::Single { value: 4 }, - } - }); - column.push(chunk); - } - let serialized: Vec = NetworkChunk::into_data(column).unwrap(); - let chunk_data = PlayClientbound::ChunkData { - value: ChunkData { - chunk_x: newly_loaded_chunk.cx, - chunk_z: newly_loaded_chunk.cz, - heightmaps, - data: Array::from(serialized.clone()), - block_entities: Array::default(), - sky_light_mask: Array::default(), - block_light_mask: Array::default(), - empty_sky_light_mask: Array::default(), - empty_block_light_mask: Array::default(), - sky_light: Array::default(), - block_light: Array::default(), - } - }; - self.send_packet(chunk_data).await; + + self.send_packet_raw(&chunk_column_data).await; } // Tell the client to unload chunks diff --git a/minecraft-server/src/prelude.rs b/minecraft-server/src/prelude.rs index 12714819..24fe879e 100644 --- a/minecraft-server/src/prelude.rs +++ b/minecraft-server/src/prelude.rs @@ -4,7 +4,7 @@ pub use log::{debug, error, info, trace, warn}; pub use minecraft_protocol::{ components::{ chat::ChatMode, - chunk::{Chunk as NetworkChunk, ChunkData, PalettedData}, + chunk::{Chunk as NetworkChunk, ChunkData as NetworkChunkColumnData, PalettedData}, difficulty::Difficulty, entity::{EntityAttribute, EntityMetadata, EntityMetadataValue}, gamemode::{Gamemode, PreviousGamemode}, diff --git a/minecraft-server/src/world/map.rs b/minecraft-server/src/world/map.rs index 92bec881..92ef3a3d 100644 --- a/minecraft-server/src/world/map.rs +++ b/minecraft-server/src/world/map.rs @@ -1,5 +1,5 @@ use std::{collections::HashMap, cmp::Ordering}; -use minecraft_protocol::{components::chunk::{PalettedData, self}, ids::blocks::Block}; +use minecraft_protocol::{components::chunk::PalettedData, ids::blocks::Block}; use tokio::sync::RwLock; use crate::prelude::*; use super::light::Light; @@ -456,16 +456,30 @@ impl WorldMap { inner_get_block(self, position).await.unwrap_or(BlockWithState::Air) } - pub async fn get_network_chunk(&self, position: ChunkPosition) -> Option { - let chunk_column_position = position.chunk_column(); - let shard = chunk_column_position.shard(self.shard_count); - let cy_in_vec: usize = position.cy.saturating_add(4).try_into().ok()?; - + pub async fn get_network_chunk_column_data(&self, position: ChunkColumnPosition) -> Option> { + let shard = position.shard(self.shard_count); let shard = self.shards[shard].read().await; - let chunk_column = shard.get(&chunk_column_position)?; - let chunk = chunk_column.chunks.get(cy_in_vec)?; - - Some(chunk.as_network_chunk().clone()) + let chunk_column = shard.get(&position)?; + + let serialized = NetworkChunk::into_data(chunk_column.chunks.iter().map(|c| c.data.clone()).collect()).unwrap(); + let chunk_data = PlayClientbound::ChunkData { value: NetworkChunkColumnData { + chunk_x: position.cx, + chunk_z: position.cz, + heightmaps: chunk_column.heightmap.to_tag(), + data: Array::from(serialized.clone()), + block_entities: Array::default(), + sky_light_mask: Array::default(), + block_light_mask: Array::default(), + empty_sky_light_mask: Array::default(), + empty_block_light_mask: Array::default(), + sky_light: Array::default(), + block_light: Array::default(), + }}; + + let chunk_data = chunk_data.serialize_minecraft_packet().map_err(|e| { + error!("Failed to serialize chunk column data: {:?}", e); + }).ok()?; + Some(chunk_data) } pub async fn get_network_heightmap(&self, position: ChunkColumnPosition) -> Option { diff --git a/minecraft-server/src/world/mod.rs b/minecraft-server/src/world/mod.rs index b9767772..4a97612b 100644 --- a/minecraft-server/src/world/mod.rs +++ b/minecraft-server/src/world/mod.rs @@ -33,8 +33,8 @@ impl World { Some(self.map.get_block(position).await) } - pub async fn get_network_chunk(&self, position: ChunkPosition) -> Option { - self.map.get_network_chunk(position).await + pub async fn get_network_chunk_column_data(&self, position: ChunkColumnPosition) -> Option> { + self.map.get_network_chunk_column_data(position).await } pub async fn get_network_heightmap(&self, position: ChunkColumnPosition) -> Option { From 985da34ae8e7df684b86c42ca672e08ad63eac6b Mon Sep 17 00:00:00 2001 From: Dimitri Date: Tue, 14 Nov 2023 08:08:30 +0100 Subject: [PATCH 26/46] Remove get heightMap network --- minecraft-server/src/world/map.rs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/minecraft-server/src/world/map.rs b/minecraft-server/src/world/map.rs index 92ef3a3d..126903ba 100644 --- a/minecraft-server/src/world/map.rs +++ b/minecraft-server/src/world/map.rs @@ -481,14 +481,6 @@ impl WorldMap { }).ok()?; Some(chunk_data) } - - pub async fn get_network_heightmap(&self, position: ChunkColumnPosition) -> Option { - let shard = position.shard(self.shard_count); - let shard = self.shards[shard].read().await; - let chunk_column = shard.get(&position)?; - - Some(chunk_column.heightmap.to_tag()) - } pub async fn set_block(&self, position: BlockPosition, block: BlockWithState) { async fn inner_get_block(s: &WorldMap, position: BlockPosition, block: BlockWithState) -> Option<()> { From 366c161ed3b8598f6021d4cb23027aaa5f060d15 Mon Sep 17 00:00:00 2001 From: Dimitri Date: Tue, 14 Nov 2023 21:32:36 +0100 Subject: [PATCH 27/46] wip pose block & update light --- minecraft-server/src/world/light.rs | 107 +++++++++++++++++++--------- minecraft-server/src/world/map.rs | 3 +- minecraft-server/src/world/mod.rs | 4 -- 3 files changed, 73 insertions(+), 41 deletions(-) diff --git a/minecraft-server/src/world/light.rs b/minecraft-server/src/world/light.rs index f5d3483c..368718c3 100644 --- a/minecraft-server/src/world/light.rs +++ b/minecraft-server/src/world/light.rs @@ -1,4 +1,4 @@ -use std::{collections::BinaryHeap, ops::AddAssign}; +use std::{collections::{BinaryHeap, VecDeque}, ops::AddAssign}; use minecraft_protocol::ids::blocks::Block; @@ -182,6 +182,29 @@ impl LightPositionInChunkColumn { bz: self.bz, } } + + pub fn get_neighbors(&self, n_chunk: usize) -> Vec { + let mut neighbors = Vec::new(); + if self.y < ((n_chunk - 1) * 16) + 1 { // No block can be higher so no block can affect the light level + neighbors.push(LightPositionInChunkColumn { bx: self.bx, y: self.y + 1, bz: self.bz }); + } + if self.bx > 0 { + neighbors.push(LightPositionInChunkColumn { bx: self.bx - 1, y: self.y, bz: self.bz }); + } + if self.bx < 15 { + neighbors.push(LightPositionInChunkColumn { bx: self.bx + 1, y: self.y, bz: self.bz }); + } + if self.bz > 0 { + neighbors.push(LightPositionInChunkColumn { bx: self.bx, y: self.y, bz: self.bz - 1 }); + } + if self.bz < 15 { + neighbors.push(LightPositionInChunkColumn { bx: self.bx, y: self.y, bz: self.bz + 1 }); + } + if self.y > 0 { + neighbors.push(LightPositionInChunkColumn { bx: self.bx, y: self.y - 1, bz: self.bz }); + } + neighbors + } } impl PartialEq for LightPositionInChunkColumn { @@ -232,8 +255,7 @@ impl std::cmp::Ord for LightPositionInChunkColumn { impl ChunkColumn { pub(super) fn init_light(&mut self) -> Result<(), ()> { - self.propagate_sky_light_inside()?; - Ok(()) + self.propagate_sky_light_inside() } fn propagate_sky_light_inside(&mut self) -> Result<(), ()> { @@ -244,7 +266,6 @@ impl ChunkColumn { self.light.sky_light.set_region(highest_blocks as usize + 16, max_y, self.light.sky_light.level)?; let mut to_explore: BinaryHeap = BinaryHeap::new(); - // Add all highest blocks to the queue for x in 0..16 { for z in 0..16 { @@ -264,27 +285,13 @@ impl ChunkColumn { fn explore_sky_light_from_heap(&mut self, to_explore: &mut BinaryHeap) -> Result<(), ()> { error!("start loop"); + // We get the neighbors and determine the light source from them + // The neighbor with the highest light level is the light source + // So we explore from it + while let Some(position) = to_explore.pop() { - let mut neighbors = Vec::new(); + let neighbors = position.get_neighbors(self.light.sky_light.sky_light_arrays.len()); let my_level = self.light.sky_light.get_level(position.clone())?; - if position.bx > 0 { - neighbors.push(LightPositionInChunkColumn { bx: position.bx - 1, y: position.y, bz: position.bz }); - } - if position.bx < 15 { - neighbors.push(LightPositionInChunkColumn { bx: position.bx + 1, y: position.y, bz: position.bz }); - } - if position.bz > 0 { - neighbors.push(LightPositionInChunkColumn { bx: position.bx, y: position.y, bz: position.bz - 1 }); - } - if position.bz < 15 { - neighbors.push(LightPositionInChunkColumn { bx: position.bx, y: position.y, bz: position.bz + 1 }); - } - if position.y > 0 { - neighbors.push(LightPositionInChunkColumn { bx: position.bx, y: position.y - 1, bz: position.bz }); - } - if position.y < ((self.light.sky_light.sky_light_arrays.len() - 1) * 16) + 1 { // No block can be higher so no block can affect the light level - neighbors.push(LightPositionInChunkColumn { bx: position.bx, y: position.y + 1, bz: position.bz }); - } for neighbor in neighbors { let neighbor_level = self.light.sky_light.get_level(neighbor.clone())?; @@ -305,19 +312,50 @@ impl ChunkColumn { Ok(()) } - pub(super) fn update_light_at(&mut self, position: BlockPositionInChunkColumn) { - let position = LightPositionInChunkColumn::from(position); - let (bx, y, bz) = (position.bx, position.y, position.bz); + fn clear_skylight_from(&mut self, position: LightPositionInChunkColumn) -> Result<(), ()> { + let mut to_explore: BinaryHeap = BinaryHeap::new(); + // We get the neighbors and determine the light source from them + // The neighbor with the highest light level is the light source + // then we clear from the other neighbors + // if are equal, we have nothing to do - let mut to_explore: BinaryHeap = BinaryHeap::from(vec![ - LightPositionInChunkColumn { bx, y: y + 1, bz }, - LightPositionInChunkColumn { bx: (bx + 1) % 16, y, bz }, - LightPositionInChunkColumn { bx: bx.saturating_sub(1), y, bz }, - LightPositionInChunkColumn { bx, y, bz: (bz + 1) % 16 }, - LightPositionInChunkColumn { bx, y, bz: bz.saturating_sub(1)}, - ]); + let my_level = self.light.sky_light.get_level(position.clone())?; + self.light.sky_light.set_level(position.clone(), my_level.saturating_sub(1))?; + to_explore.push(position.clone()); + + while let Some(position) = to_explore.pop() { + let neighbors = position.get_neighbors(self.light.sky_light.sky_light_arrays.len()); + let my_level = self.light.sky_light.get_level(position.clone())?; + let my_is_inside = false; // get it - self.explore_sky_light_from_heap(&mut to_explore).map_err(|_| error!("Error while updating light")).unwrap(); + for neighbor in neighbors { + let neighbor_level = self.light.sky_light.get_level(neighbor.clone()).unwrap(); + + let block = Block::from(self.get_block(neighbor.clone().into())); + + if block.is_transparent() + && ((my_is_inside && neighbor_level <= my_level) + || (!my_is_inside && neighbor_level < my_level)) + { + let highest_block = self.get_highest_block_at(&neighbor.clone().into()) + 16; + let is_inside = highest_block > neighbor.y as u16 + 1; + to_explore.push(neighbor.clone()); + let new_level = if is_inside { my_level - block.light_absorption() - 1 } else { self.light.sky_light.level }; + self.light.sky_light.set_level(neighbor, new_level)?; + } + } + } + Ok(()) + } + + pub(super) fn update_light_as_block_changed_at(&mut self, position: BlockPositionInChunkColumn, blocking: bool) { + let position = LightPositionInChunkColumn::from(position); + + let mut to_explore: BinaryHeap = BinaryHeap::from(position.get_neighbors(self.light.sky_light.sky_light_arrays.len())); + if blocking { + let _ = self.clear_skylight_from(position.clone()).map_err(|_| error!("Error while updating light")); + } + let _ = self.explore_sky_light_from_heap(&mut to_explore).map_err(|_| error!("Error while updating light")); } } @@ -423,6 +461,5 @@ mod tests { flat_chunk.set_block_for_test(BlockPositionInChunkColumn { bx: 0, y: -51, bz: 2 }, Block::Air.into()); assert_eq!(flat_chunk.light.sky_light.sky_light_arrays[1].get(BlockPositionInChunk { bx: 0, by: 13, bz: 2 }).unwrap(), 12); - } } diff --git a/minecraft-server/src/world/map.rs b/minecraft-server/src/world/map.rs index 126903ba..41757f48 100644 --- a/minecraft-server/src/world/map.rs +++ b/minecraft-server/src/world/map.rs @@ -429,7 +429,7 @@ impl ChunkColumn { }, _ => {} } - self.update_light_at(position); + self.update_light_as_block_changed_at(position, !not_filter_sunlight); } } @@ -492,7 +492,6 @@ impl WorldMap { let mut shard = s.shards[shard].write().await; let chunk_column = shard.get_mut(&chunk_column_position)?; chunk_column.set_block(position_in_chunk_column.clone(), block); - chunk_column.update_light_at(position_in_chunk_column); Some(()) } inner_get_block(self, position, block).await; diff --git a/minecraft-server/src/world/mod.rs b/minecraft-server/src/world/mod.rs index 4a97612b..05be027f 100644 --- a/minecraft-server/src/world/mod.rs +++ b/minecraft-server/src/world/mod.rs @@ -37,10 +37,6 @@ impl World { self.map.get_network_chunk_column_data(position).await } - pub async fn get_network_heightmap(&self, position: ChunkColumnPosition) -> Option { - self.map.get_network_heightmap(position).await - } - pub async fn set_block(&self, position: BlockPosition, block: BlockWithState) { self.map.set_block(position.clone(), block.clone()).await; self.notify(&position.chunk_column(), WorldChange::BlockChange(position, block)).await; From 903eaa1f46136b7fde8de1f9ed88789e078a6b7b Mon Sep 17 00:00:00 2001 From: DimitriTimoz Date: Tue, 14 Nov 2023 22:47:52 +0100 Subject: [PATCH 28/46] roll back --- .../src/player_handler/handshake.rs | 40 ++++++++++++++--- minecraft-server/src/player_handler/play.rs | 45 +++++++++++++++---- minecraft-server/src/world/light.rs | 2 - minecraft-server/src/world/map.rs | 35 +++++---------- minecraft-server/src/world/mod.rs | 4 +- 5 files changed, 83 insertions(+), 43 deletions(-) diff --git a/minecraft-server/src/player_handler/handshake.rs b/minecraft-server/src/player_handler/handshake.rs index 1ec59b07..2d600000 100644 --- a/minecraft-server/src/player_handler/handshake.rs +++ b/minecraft-server/src/player_handler/handshake.rs @@ -314,17 +314,43 @@ pub async fn handshake(stream: &mut TcpStream, logged_in_player_info: LoggedInPl } } world.update_loaded_chunks(logged_in_player_info.uuid, loaded_chunks).await; - + let mut heightmaps = HashMap::new(); + heightmaps.insert(String::from("MOTION_BLOCKING"), NbtTag::LongArray(vec![0; 37])); + let heightmaps = NbtTag::Compound(heightmaps); for cx in -3..=3 { for cz in -3..=3 { - let chunk_column = world.get_network_chunk_column_data(ChunkColumnPosition { cx, cz }).await.unwrap_or_else(|| { - error!("Chunk not loaded: {cx} {cz}"); - panic!("Chunk not loaded: {cx} {cz}"); - }); - - send_packet_raw(stream, &chunk_column).await; + let mut column = Vec::new(); + for cy in -4..20 { + let chunk = world.get_network_chunk(ChunkPosition { cx, cy, cz }).await.unwrap_or_else(|| { + error!("Chunk not loaded: {cx} {cy} {cz}"); + NetworkChunk { // TODO hard error + block_count: 0, + blocks: PalettedData::Single { value: 0 }, + biomes: PalettedData::Single { value: 4 }, + } + }); + column.push(chunk); + } + let serialized: Vec = NetworkChunk::into_data(column).unwrap(); + let chunk_data = PlayClientbound::ChunkData { + value: NetworkChunkColumnData { + chunk_x: cx, + chunk_z: cz, + heightmaps: heightmaps.clone(), + data: Array::from(serialized.clone()), + block_entities: Array::default(), + sky_light_mask: Array::default(), + block_light_mask: Array::default(), + empty_sky_light_mask: Array::default(), + empty_block_light_mask: Array::default(), + sky_light: Array::default(), + block_light: Array::default(), + } + }; + send_packet(stream, chunk_data).await; } } + debug!("ChunkData sent"); // Chunk batch end diff --git a/minecraft-server/src/player_handler/play.rs b/minecraft-server/src/player_handler/play.rs index 459addf9..a58d90dc 100644 --- a/minecraft-server/src/player_handler/play.rs +++ b/minecraft-server/src/player_handler/play.rs @@ -1,3 +1,5 @@ +use minecraft_protocol::components::chunk; + use super::*; struct PlayerHandler { @@ -21,8 +23,8 @@ impl PlayerHandler { self.packet_sender.send(packet).await.unwrap(); } - async fn send_packet_raw(&mut self, packet: &[u8]) { - self.packet_sender.send(packet.to_vec()).await.unwrap(); + async fn send_packet_raw(&mut self, packet: Vec) { + self.packet_sender.send(packet).await.unwrap(); } async fn on_server_message(&mut self, message: ServerMessage) { @@ -75,13 +77,39 @@ impl PlayerHandler { self.world.update_loaded_chunks(self.info.uuid, loaded_chunks_after.clone()).await; // Send the chunks to the client + let mut heightmaps = HashMap::new(); + heightmaps.insert(String::from("MOTION_BLOCKING"), NbtTag::LongArray(vec![0; 37])); + let heightmaps = NbtTag::Compound(heightmaps); for newly_loaded_chunk in newly_loaded_chunks { - let chunk_column_data = self.world.get_network_chunk_column_data(newly_loaded_chunk.clone()).await.unwrap_or_else(|| { - error!("Chunk not loaded: {newly_loaded_chunk:?}"); - panic!("Chunk not loaded: {newly_loaded_chunk:?}"); - }); - - self.send_packet_raw(&chunk_column_data).await; + let mut column = Vec::new(); + for cy in -4..20 { + let chunk = self.world.get_network_chunk(newly_loaded_chunk.chunk(cy)).await.unwrap_or_else(|| { + error!("Chunk not loaded: {newly_loaded_chunk:?}"); + NetworkChunk { // TODO hard error + block_count: 0, + blocks: PalettedData::Single { value: 0 }, + biomes: PalettedData::Single { value: 4 }, + } + }); + column.push(chunk); + } + let serialized: Vec = NetworkChunk::into_data(column).unwrap(); + let chunk_data = PlayClientbound::ChunkData { + value: NetworkChunkColumnData { + chunk_x: newly_loaded_chunk.cx, + chunk_z: newly_loaded_chunk.cz, + heightmaps: heightmaps.clone(), + data: Array::from(serialized.clone()), + block_entities: Array::default(), + sky_light_mask: Array::default(), + block_light_mask: Array::default(), + empty_sky_light_mask: Array::default(), + empty_block_light_mask: Array::default(), + sky_light: Array::default(), + block_light: Array::default(), + } + }; + self.send_packet(chunk_data).await; } // Tell the client to unload chunks @@ -93,6 +121,7 @@ impl PlayerHandler { } self.loaded_chunks = loaded_chunks_after; + } async fn on_packet<'a>(&mut self, packet: PlayServerbound<'a>) { diff --git a/minecraft-server/src/world/light.rs b/minecraft-server/src/world/light.rs index 368718c3..a677e6d5 100644 --- a/minecraft-server/src/world/light.rs +++ b/minecraft-server/src/world/light.rs @@ -284,7 +284,6 @@ impl ChunkColumn { } fn explore_sky_light_from_heap(&mut self, to_explore: &mut BinaryHeap) -> Result<(), ()> { - error!("start loop"); // We get the neighbors and determine the light source from them // The neighbor with the highest light level is the light source // So we explore from it @@ -308,7 +307,6 @@ impl ChunkColumn { } } } - error!("end loop"); Ok(()) } diff --git a/minecraft-server/src/world/map.rs b/minecraft-server/src/world/map.rs index 41757f48..a464cc1a 100644 --- a/minecraft-server/src/world/map.rs +++ b/minecraft-server/src/world/map.rs @@ -1,4 +1,4 @@ -use std::{collections::HashMap, cmp::Ordering}; +use std::{collections::HashMap, cmp::Ordering, vec}; use minecraft_protocol::{components::chunk::PalettedData, ids::blocks::Block}; use tokio::sync::RwLock; use crate::prelude::*; @@ -429,6 +429,7 @@ impl ChunkColumn { }, _ => {} } + trace!("setting"); self.update_light_as_block_changed_at(position, !not_filter_sunlight); } } @@ -456,30 +457,16 @@ impl WorldMap { inner_get_block(self, position).await.unwrap_or(BlockWithState::Air) } - pub async fn get_network_chunk_column_data(&self, position: ChunkColumnPosition) -> Option> { - let shard = position.shard(self.shard_count); + pub async fn get_network_chunk(&self, position: ChunkPosition) -> Option { + let chunk_column_position = position.chunk_column(); + let shard = chunk_column_position.shard(self.shard_count); + let cy_in_vec: usize = position.cy.saturating_add(4).try_into().ok()?; + let shard = self.shards[shard].read().await; - let chunk_column = shard.get(&position)?; - - let serialized = NetworkChunk::into_data(chunk_column.chunks.iter().map(|c| c.data.clone()).collect()).unwrap(); - let chunk_data = PlayClientbound::ChunkData { value: NetworkChunkColumnData { - chunk_x: position.cx, - chunk_z: position.cz, - heightmaps: chunk_column.heightmap.to_tag(), - data: Array::from(serialized.clone()), - block_entities: Array::default(), - sky_light_mask: Array::default(), - block_light_mask: Array::default(), - empty_sky_light_mask: Array::default(), - empty_block_light_mask: Array::default(), - sky_light: Array::default(), - block_light: Array::default(), - }}; - - let chunk_data = chunk_data.serialize_minecraft_packet().map_err(|e| { - error!("Failed to serialize chunk column data: {:?}", e); - }).ok()?; - Some(chunk_data) + let chunk_column = shard.get(&chunk_column_position)?; + let chunk = chunk_column.chunks.get(cy_in_vec)?; + + Some(chunk.as_network_chunk().clone()) } pub async fn set_block(&self, position: BlockPosition, block: BlockWithState) { diff --git a/minecraft-server/src/world/mod.rs b/minecraft-server/src/world/mod.rs index 05be027f..081614c1 100644 --- a/minecraft-server/src/world/mod.rs +++ b/minecraft-server/src/world/mod.rs @@ -33,8 +33,8 @@ impl World { Some(self.map.get_block(position).await) } - pub async fn get_network_chunk_column_data(&self, position: ChunkColumnPosition) -> Option> { - self.map.get_network_chunk_column_data(position).await + pub async fn get_network_chunk(&self, position: ChunkPosition) -> Option { + self.map.get_network_chunk(position).await } pub async fn set_block(&self, position: BlockPosition, block: BlockWithState) { From 840923928e6ad8756982217bf26243470afed1aa Mon Sep 17 00:00:00 2001 From: DimitriTimoz Date: Sat, 18 Nov 2023 02:05:25 +0100 Subject: [PATCH 29/46] fix load amount chunk --- .../src/player_handler/handshake.rs | 1 - minecraft-server/src/player_handler/play.rs | 17 ++++++++++++----- minecraft-server/src/world/light.rs | 4 ++-- minecraft-server/src/world/map.rs | 13 +++++++++++++ 4 files changed, 27 insertions(+), 8 deletions(-) diff --git a/minecraft-server/src/player_handler/handshake.rs b/minecraft-server/src/player_handler/handshake.rs index 2d600000..7181b7e2 100644 --- a/minecraft-server/src/player_handler/handshake.rs +++ b/minecraft-server/src/player_handler/handshake.rs @@ -366,7 +366,6 @@ pub async fn handshake(stream: &mut TcpStream, logged_in_player_info: LoggedInPl return Err(()); }; debug!("ChunkBatchAcknoledgement received"); - Ok((PlayerInfo { addr: logged_in_player_info.addr, username: logged_in_player_info.username, diff --git a/minecraft-server/src/player_handler/play.rs b/minecraft-server/src/player_handler/play.rs index a58d90dc..4e1cdff4 100644 --- a/minecraft-server/src/player_handler/play.rs +++ b/minecraft-server/src/player_handler/play.rs @@ -45,7 +45,6 @@ impl PlayerHandler { async fn on_move(&mut self) { let new_center_chunk = self.position.chunk(); - // Tell the client which chunk he is in if new_center_chunk == self.center_chunk { return }; self.send_packet(PlayClientbound::SetCenterChunk { chunk_x: VarInt(new_center_chunk.cx), chunk_z: VarInt(new_center_chunk.cz) }).await; @@ -58,8 +57,8 @@ impl PlayerHandler { let mut loaded_chunks_after = HashSet::new(); for cx in (new_center_chunk.cx - self.render_distance)..=(new_center_chunk.cx + self.render_distance) { for cz in (new_center_chunk.cz - self.render_distance)..=(new_center_chunk.cz + self.render_distance) { - let dist = (((cx - new_center_chunk.cx).pow(2) + (cz - new_center_chunk.cz).pow(2)) as f32).sqrt(); - if dist > self.render_distance as f32 { continue }; + let dist = (cx - new_center_chunk.cx).abs() + (cz - new_center_chunk.cz).abs(); + if dist > self.render_distance { continue }; loaded_chunks_after.insert(ChunkColumnPosition { cx, cz }); } } @@ -68,10 +67,10 @@ impl PlayerHandler { if loaded_chunks_after == self.loaded_chunks { return }; let mut newly_loaded_chunks: Vec<_> = loaded_chunks_after.difference(&self.loaded_chunks).cloned().collect(); let unloaded_chunks: Vec<_> = self.loaded_chunks.difference(&loaded_chunks_after).cloned().collect(); - for skipped in newly_loaded_chunks.iter().skip(50) { + for skipped in newly_loaded_chunks.iter().skip(5) { loaded_chunks_after.remove(skipped); } - newly_loaded_chunks.truncate(50); + newly_loaded_chunks.truncate(5); // Tell the world about the changes self.world.update_loaded_chunks(self.info.uuid, loaded_chunks_after.clone()).await; @@ -80,6 +79,8 @@ impl PlayerHandler { let mut heightmaps = HashMap::new(); heightmaps.insert(String::from("MOTION_BLOCKING"), NbtTag::LongArray(vec![0; 37])); let heightmaps = NbtTag::Compound(heightmaps); + let start_time = std::time::Instant::now(); + let mut i = 0; for newly_loaded_chunk in newly_loaded_chunks { let mut column = Vec::new(); for cy in -4..20 { @@ -109,9 +110,15 @@ impl PlayerHandler { block_light: Array::default(), } }; + i += 1; + let elapsed: Duration = start_time.elapsed(); + info!("Chunk {} Elapsed: {:?}", i, elapsed); + self.send_packet(chunk_data).await; + info!("sent"); } + // Tell the client to unload chunks for unloaded_chunk in unloaded_chunks { self.send_packet(PlayClientbound::UnloadChunk { diff --git a/minecraft-server/src/world/light.rs b/minecraft-server/src/world/light.rs index a677e6d5..b30aef8d 100644 --- a/minecraft-server/src/world/light.rs +++ b/minecraft-server/src/world/light.rs @@ -1,4 +1,4 @@ -use std::{collections::{BinaryHeap, VecDeque}, ops::AddAssign}; +use std::{collections::BinaryHeap, ops::AddAssign}; use minecraft_protocol::ids::blocks::Block; @@ -6,7 +6,7 @@ use crate::prelude::*; use super::*; #[derive(Debug, Clone)] -struct SectionLightData(Vec); +struct SectionLightData(Vec); // TODO(optimization): Use simd impl SectionLightData { pub fn new() -> SectionLightData { diff --git a/minecraft-server/src/world/map.rs b/minecraft-server/src/world/map.rs index a464cc1a..b1827b3f 100644 --- a/minecraft-server/src/world/map.rs +++ b/minecraft-server/src/world/map.rs @@ -698,5 +698,18 @@ mod tests { } + #[test] + fn benchmark_get_block() { + + let start_time = std::time::Instant::now(); + for _ in 0..441 { + let mut column = ChunkColumn::flat(); + } + + let elapsed: Duration = start_time.elapsed(); + println!("All Elapsed: {:?}", elapsed); + println!("Elapsed: {:?}", elapsed / 441); + } + } From dbfccb3caf2b9230e21e96a3628a331b284a21ac Mon Sep 17 00:00:00 2001 From: DimitriTimoz Date: Sat, 18 Nov 2023 17:36:48 +0100 Subject: [PATCH 30/46] Flat world update sky light --- .../src/player_handler/handshake.rs | 41 ++---- minecraft-server/src/player_handler/play.rs | 45 +----- minecraft-server/src/world/light.rs | 135 ++++++++++++------ minecraft-server/src/world/map.rs | 31 ++-- minecraft-server/src/world/mod.rs | 4 +- 5 files changed, 129 insertions(+), 127 deletions(-) diff --git a/minecraft-server/src/player_handler/handshake.rs b/minecraft-server/src/player_handler/handshake.rs index 7181b7e2..b0602e4e 100644 --- a/minecraft-server/src/player_handler/handshake.rs +++ b/minecraft-server/src/player_handler/handshake.rs @@ -307,6 +307,7 @@ pub async fn handshake(stream: &mut TcpStream, logged_in_player_info: LoggedInPl debug!("ChunkBatchStart sent"); let change_receiver = world.add_loader(logged_in_player_info.uuid).await; + // TODO: Move chunk loading let mut loaded_chunks = HashSet::new(); for cx in -3..=3 { for cz in -3..=3 { @@ -314,40 +315,16 @@ pub async fn handshake(stream: &mut TcpStream, logged_in_player_info: LoggedInPl } } world.update_loaded_chunks(logged_in_player_info.uuid, loaded_chunks).await; - let mut heightmaps = HashMap::new(); - heightmaps.insert(String::from("MOTION_BLOCKING"), NbtTag::LongArray(vec![0; 37])); - let heightmaps = NbtTag::Compound(heightmaps); + + for cx in -3..=3 { for cz in -3..=3 { - let mut column = Vec::new(); - for cy in -4..20 { - let chunk = world.get_network_chunk(ChunkPosition { cx, cy, cz }).await.unwrap_or_else(|| { - error!("Chunk not loaded: {cx} {cy} {cz}"); - NetworkChunk { // TODO hard error - block_count: 0, - blocks: PalettedData::Single { value: 0 }, - biomes: PalettedData::Single { value: 4 }, - } - }); - column.push(chunk); - } - let serialized: Vec = NetworkChunk::into_data(column).unwrap(); - let chunk_data = PlayClientbound::ChunkData { - value: NetworkChunkColumnData { - chunk_x: cx, - chunk_z: cz, - heightmaps: heightmaps.clone(), - data: Array::from(serialized.clone()), - block_entities: Array::default(), - sky_light_mask: Array::default(), - block_light_mask: Array::default(), - empty_sky_light_mask: Array::default(), - empty_block_light_mask: Array::default(), - sky_light: Array::default(), - block_light: Array::default(), - } - }; - send_packet(stream, chunk_data).await; + let chunk_column = world.get_network_chunk_column_data(ChunkColumnPosition { cx, cz }).await.unwrap_or_else(|| { + error!("Chunk not loaded: {cx} {cz}"); + panic!("Chunk not loaded: {cx} {cz}"); + }); + + send_packet_raw(stream, &chunk_column).await; } } diff --git a/minecraft-server/src/player_handler/play.rs b/minecraft-server/src/player_handler/play.rs index 4e1cdff4..f54278fb 100644 --- a/minecraft-server/src/player_handler/play.rs +++ b/minecraft-server/src/player_handler/play.rs @@ -1,5 +1,3 @@ -use minecraft_protocol::components::chunk; - use super::*; struct PlayerHandler { @@ -76,45 +74,14 @@ impl PlayerHandler { self.world.update_loaded_chunks(self.info.uuid, loaded_chunks_after.clone()).await; // Send the chunks to the client - let mut heightmaps = HashMap::new(); - heightmaps.insert(String::from("MOTION_BLOCKING"), NbtTag::LongArray(vec![0; 37])); - let heightmaps = NbtTag::Compound(heightmaps); - let start_time = std::time::Instant::now(); - let mut i = 0; for newly_loaded_chunk in newly_loaded_chunks { - let mut column = Vec::new(); - for cy in -4..20 { - let chunk = self.world.get_network_chunk(newly_loaded_chunk.chunk(cy)).await.unwrap_or_else(|| { - error!("Chunk not loaded: {newly_loaded_chunk:?}"); - NetworkChunk { // TODO hard error - block_count: 0, - blocks: PalettedData::Single { value: 0 }, - biomes: PalettedData::Single { value: 4 }, - } - }); - column.push(chunk); - } - let serialized: Vec = NetworkChunk::into_data(column).unwrap(); - let chunk_data = PlayClientbound::ChunkData { - value: NetworkChunkColumnData { - chunk_x: newly_loaded_chunk.cx, - chunk_z: newly_loaded_chunk.cz, - heightmaps: heightmaps.clone(), - data: Array::from(serialized.clone()), - block_entities: Array::default(), - sky_light_mask: Array::default(), - block_light_mask: Array::default(), - empty_sky_light_mask: Array::default(), - empty_block_light_mask: Array::default(), - sky_light: Array::default(), - block_light: Array::default(), - } - }; - i += 1; - let elapsed: Duration = start_time.elapsed(); - info!("Chunk {} Elapsed: {:?}", i, elapsed); - self.send_packet(chunk_data).await; + let chunk_column_data = self.world.get_network_chunk_column_data(newly_loaded_chunk.clone()).await.unwrap_or_else(|| { + error!("Chunk not loaded: {newly_loaded_chunk:?}"); + panic!("Chunk not loaded: {newly_loaded_chunk:?}"); + }); + self.send_packet_raw(chunk_column_data).await; + info!("sent"); } diff --git a/minecraft-server/src/world/light.rs b/minecraft-server/src/world/light.rs index b30aef8d..013223dd 100644 --- a/minecraft-server/src/world/light.rs +++ b/minecraft-server/src/world/light.rs @@ -82,22 +82,50 @@ impl SectionLightData { } #[derive(Debug, Clone)] -struct SkyLight { +struct LightSystem { /// The level of the sky light, 15 is the maximum. pub level: u8, /// The sky light data for each section. - pub sky_light_arrays: Vec, + pub light_arrays: Vec, /// The mask of sections that have sky light data. - pub sky_light_mask: u64, + pub light_mask: u64, /// The mask of sections that don't have sky light data. - pub empty_sky_light_mask: u64, + pub empty_light_mask: u64, zero_chunk_index: usize, } -impl SkyLight { - pub fn to_packet(&self) -> () { - +impl LightSystem { + /// Get the light data as an array of arrays. + fn to_array<'a>(&self) -> Array<'a, Array<'a, u8, VarInt>, VarInt> { + let mut sections = Vec::new(); + for (i, section) in self.light_arrays.iter().enumerate() { + if self.light_mask & (1 << i) != 0 { + let mut data = Vec::new(); + for byte in section.0.iter() { + data.push(*byte); + } + sections.push(Array::from(data)); + } + } + Array::from(sections) + } + + /// Get the light mask and the empty light mask as bitsets. + /// return (light_mask, empty_light_mask) + fn masks_to_bitset<'a>(&self) -> (BitSet<'a>, BitSet<'a>) { + let mut light_mask = BitSet::from(vec![self.light_mask as i64]); + let empty_light_mask = BitSet::from(vec![self.empty_light_mask as i64]); + (light_mask, empty_light_mask) + } + + /// Get the light data and the light mask and the empty light mask as bitsets. + /// return (light_data, light_mask, empty_light_mask) + pub fn get_packet_data<'a>(&self) -> (Array<'a, Array<'a, u8, VarInt>, VarInt>, BitSet<'a>, BitSet<'a>) { + let data = self.to_array(); + let (light_mask, empty_light_mask) = self.masks_to_bitset(); + (data, light_mask, empty_light_mask) } + /// Set the sky light in the given section. pub fn set_region(&mut self, from_y: usize, to_y: usize, level: u8) -> Result<(), ()> { if level > self.level { @@ -115,22 +143,24 @@ impl SkyLight { for section in first_section..=last_section { if section != first_section && section != last_section { // Set the whole section - self.sky_light_arrays[section].set_with(level); + self.light_arrays[section].set_with(level); } else { // Set the part of the section let first_offset = if section == first_section { first_secion_offset } else { 0 }; let last_offset = if section == last_section { last_section_offset } else { 15 }; for y in first_offset..=last_offset { - self.sky_light_arrays[section].set_layer(y as u8, level)?; + self.light_arrays[section].set_layer(y as u8, level)?; } } // Update the mask let mask = 1 << section; if self.level > 0 { - self.empty_sky_light_mask |= mask; + self.empty_light_mask &= !mask; + self.light_mask |= mask; } else { - self.empty_sky_light_mask &= !mask; + self.empty_light_mask |= mask; + self.light_mask &= !mask; } } @@ -139,32 +169,47 @@ impl SkyLight { pub(super) fn get_level(&self, position: LightPositionInChunkColumn) -> Result { let section = position.y.div_euclid(16); - self.sky_light_arrays[section.max(0)].get(position.in_chunk()) + self.light_arrays[section.max(0)].get(position.in_chunk()) } pub(super) fn set_level(&mut self, position: LightPositionInChunkColumn, level: u8) -> Result<(), ()> { let section = position.y.div_euclid(16); - self.sky_light_arrays[section.max(0)].set(position.in_chunk(), level) + // Update the mask + let mask = 1 << section; + if self.level > 0 { + self.empty_light_mask &= !mask; + self.light_mask |= mask; + } else { + // TODO: don't apply this if another block contains the light + self.empty_light_mask |= mask; + self.light_mask &= !mask; + } + self.light_arrays[section.max(0)].set(position.in_chunk(), level) } } pub(super) struct Light { - sky_light: SkyLight, + sky_light: LightSystem, } impl Light { pub fn new() -> Self { // TODO: Make this configurable with the world. Self { - sky_light: SkyLight { + sky_light: LightSystem { level: 15, - sky_light_arrays: vec![SectionLightData::new(); 24+2], - sky_light_mask: 0, - empty_sky_light_mask: !0, + light_arrays: vec![SectionLightData::new(); 24+2], + light_mask: 0, + empty_light_mask: !0, zero_chunk_index: 4, // We start at y=-64, and we have a chunk under that. }, } } + + pub fn get_packet(&self) -> (Array, VarInt>, BitSet, BitSet) { + self.sky_light.get_packet_data() + } + } #[derive(Debug, Clone)] @@ -262,7 +307,7 @@ impl ChunkColumn { // Set all highest blocks to the highest block let highest_blocks = self.get_highest_block(); - let max_y = self.light.sky_light.sky_light_arrays.len() * 16 - 1; + let max_y = self.light.sky_light.light_arrays.len() * 16 - 1; self.light.sky_light.set_region(highest_blocks as usize + 16, max_y, self.light.sky_light.level)?; let mut to_explore: BinaryHeap = BinaryHeap::new(); @@ -289,7 +334,7 @@ impl ChunkColumn { // So we explore from it while let Some(position) = to_explore.pop() { - let neighbors = position.get_neighbors(self.light.sky_light.sky_light_arrays.len()); + let neighbors = position.get_neighbors(self.light.sky_light.light_arrays.len()); let my_level = self.light.sky_light.get_level(position.clone())?; for neighbor in neighbors { @@ -310,7 +355,7 @@ impl ChunkColumn { Ok(()) } - fn clear_skylight_from(&mut self, position: LightPositionInChunkColumn) -> Result<(), ()> { + fn clear_lightsystem_from(&mut self, position: LightPositionInChunkColumn) -> Result<(), ()> { let mut to_explore: BinaryHeap = BinaryHeap::new(); // We get the neighbors and determine the light source from them // The neighbor with the highest light level is the light source @@ -322,7 +367,7 @@ impl ChunkColumn { to_explore.push(position.clone()); while let Some(position) = to_explore.pop() { - let neighbors = position.get_neighbors(self.light.sky_light.sky_light_arrays.len()); + let neighbors = position.get_neighbors(self.light.sky_light.light_arrays.len()); let my_level = self.light.sky_light.get_level(position.clone())?; let my_is_inside = false; // get it @@ -349,9 +394,9 @@ impl ChunkColumn { pub(super) fn update_light_as_block_changed_at(&mut self, position: BlockPositionInChunkColumn, blocking: bool) { let position = LightPositionInChunkColumn::from(position); - let mut to_explore: BinaryHeap = BinaryHeap::from(position.get_neighbors(self.light.sky_light.sky_light_arrays.len())); + let mut to_explore: BinaryHeap = BinaryHeap::from(position.get_neighbors(self.light.sky_light.light_arrays.len())); if blocking { - let _ = self.clear_skylight_from(position.clone()).map_err(|_| error!("Error while updating light")); + let _ = self.clear_lightsystem_from(position.clone()).map_err(|_| error!("Error while updating light")); } let _ = self.explore_sky_light_from_heap(&mut to_explore).map_err(|_| error!("Error while updating light")); } @@ -407,25 +452,25 @@ mod tests { #[test] fn test_set_region() { - let mut sky_light = SkyLight { + let mut sky_light = LightSystem { level: 15, - sky_light_arrays: vec![SectionLightData::new(); 16+2], - sky_light_mask: 0, - empty_sky_light_mask: !0, + light_arrays: vec![SectionLightData::new(); 16+2], + light_mask: 0, + empty_light_mask: !0, zero_chunk_index: 4, // We start at y=-64, and we have a chunk under that. }; sky_light.set_region(1, 33, 15).unwrap(); // Test in - assert_eq!(sky_light.sky_light_arrays[0].get(BlockPositionInChunk { bx: 0, by: 1, bz: 7 }).unwrap(), 15); - assert_eq!(sky_light.sky_light_arrays[1].get(BlockPositionInChunk { bx: 1, by: 15, bz: 8 }).unwrap(), 15); - assert_eq!(sky_light.sky_light_arrays[2].get(BlockPositionInChunk { bx: 3, by: 0, bz: 0 }).unwrap(), 15); + assert_eq!(sky_light.light_arrays[0].get(BlockPositionInChunk { bx: 0, by: 1, bz: 7 }).unwrap(), 15); + assert_eq!(sky_light.light_arrays[1].get(BlockPositionInChunk { bx: 1, by: 15, bz: 8 }).unwrap(), 15); + assert_eq!(sky_light.light_arrays[2].get(BlockPositionInChunk { bx: 3, by: 0, bz: 0 }).unwrap(), 15); // Test out - assert_eq!(sky_light.sky_light_arrays[0].get(BlockPositionInChunk { bx: 4, by: 0, bz: 2 }).unwrap(), 0); - assert_eq!(sky_light.sky_light_arrays[3].get(BlockPositionInChunk { bx: 0, by: 14, bz: 9 }).unwrap(), 0); - assert_eq!(sky_light.sky_light_arrays[4].get(BlockPositionInChunk { bx: 9, by: 0, bz: 10 }).unwrap(), 0); + assert_eq!(sky_light.light_arrays[0].get(BlockPositionInChunk { bx: 4, by: 0, bz: 2 }).unwrap(), 0); + assert_eq!(sky_light.light_arrays[3].get(BlockPositionInChunk { bx: 0, by: 14, bz: 9 }).unwrap(), 0); + assert_eq!(sky_light.light_arrays[4].get(BlockPositionInChunk { bx: 9, by: 0, bz: 10 }).unwrap(), 0); } #[test] @@ -435,29 +480,29 @@ mod tests { // Check that the sky light is equal to the light level above the grass and on the top of the world. for x in 0..16 { for z in 0..16 { - assert_eq!(flat_chunk.light.sky_light.sky_light_arrays[0].get(BlockPositionInChunk { bx: x, by: 0, bz: z }).unwrap(), 0); - assert_eq!(flat_chunk.light.sky_light.sky_light_arrays[4].get(BlockPositionInChunk { bx: x, by: 0, bz: z }).unwrap(), 15); - assert_eq!(flat_chunk.light.sky_light.sky_light_arrays[25].get(BlockPositionInChunk { bx: x, by: 15, bz: z }).unwrap(), 15); + assert_eq!(flat_chunk.light.sky_light.light_arrays[0].get(BlockPositionInChunk { bx: x, by: 0, bz: z }).unwrap(), 0); + assert_eq!(flat_chunk.light.sky_light.light_arrays[4].get(BlockPositionInChunk { bx: x, by: 0, bz: z }).unwrap(), 15); + assert_eq!(flat_chunk.light.sky_light.light_arrays[25].get(BlockPositionInChunk { bx: x, by: 15, bz: z }).unwrap(), 15); } } // Break the grass block and check that the sky light is correct. - assert_eq!(flat_chunk.light.sky_light.sky_light_arrays[1].get(BlockPositionInChunk { bx: 0, by: 15, bz: 0 }).unwrap(), 0); + assert_eq!(flat_chunk.light.sky_light.light_arrays[1].get(BlockPositionInChunk { bx: 0, by: 15, bz: 0 }).unwrap(), 0); flat_chunk.set_block_for_test(BlockPositionInChunkColumn { bx: 0, y: -49, bz: 0 }, Block::Air.into()); - assert_eq!(flat_chunk.light.sky_light.sky_light_arrays[1].get(BlockPositionInChunk { bx: 0, by: 15, bz: 0 }).unwrap(), 15); + assert_eq!(flat_chunk.light.sky_light.light_arrays[1].get(BlockPositionInChunk { bx: 0, by: 15, bz: 0 }).unwrap(), 15); - assert_eq!(flat_chunk.light.sky_light.sky_light_arrays[1].get(BlockPositionInChunk { bx: 0, by: 14, bz: 0 }).unwrap(), 0); + assert_eq!(flat_chunk.light.sky_light.light_arrays[1].get(BlockPositionInChunk { bx: 0, by: 14, bz: 0 }).unwrap(), 0); flat_chunk.set_block_for_test(BlockPositionInChunkColumn { bx: 0, y: -50, bz: 0 }, Block::Air.into()); - assert_eq!(flat_chunk.light.sky_light.sky_light_arrays[1].get(BlockPositionInChunk { bx: 0, by: 14, bz: 0 }).unwrap(), 15); + assert_eq!(flat_chunk.light.sky_light.light_arrays[1].get(BlockPositionInChunk { bx: 0, by: 14, bz: 0 }).unwrap(), 15); flat_chunk.set_block_for_test(BlockPositionInChunkColumn { bx: 0, y: -50, bz: 1 }, Block::Air.into()); - assert_eq!(flat_chunk.light.sky_light.sky_light_arrays[1].get(BlockPositionInChunk { bx: 0, by: 14, bz: 1 }).unwrap(), 14); - assert_eq!(flat_chunk.light.sky_light.sky_light_arrays[1].get(BlockPositionInChunk { bx: 0, by: 0, bz: 10 }).unwrap(), 0); + assert_eq!(flat_chunk.light.sky_light.light_arrays[1].get(BlockPositionInChunk { bx: 0, by: 14, bz: 1 }).unwrap(), 14); + assert_eq!(flat_chunk.light.sky_light.light_arrays[1].get(BlockPositionInChunk { bx: 0, by: 0, bz: 10 }).unwrap(), 0); flat_chunk.set_block_for_test(BlockPositionInChunkColumn { bx: 0, y: -50, bz: 2 }, Block::Air.into()); - assert_eq!(flat_chunk.light.sky_light.sky_light_arrays[1].get(BlockPositionInChunk { bx: 0, by: 14, bz: 2 }).unwrap(), 13); + assert_eq!(flat_chunk.light.sky_light.light_arrays[1].get(BlockPositionInChunk { bx: 0, by: 14, bz: 2 }).unwrap(), 13); flat_chunk.set_block_for_test(BlockPositionInChunkColumn { bx: 0, y: -51, bz: 2 }, Block::Air.into()); - assert_eq!(flat_chunk.light.sky_light.sky_light_arrays[1].get(BlockPositionInChunk { bx: 0, by: 13, bz: 2 }).unwrap(), 12); + assert_eq!(flat_chunk.light.sky_light.light_arrays[1].get(BlockPositionInChunk { bx: 0, by: 13, bz: 2 }).unwrap(), 12); } } diff --git a/minecraft-server/src/world/map.rs b/minecraft-server/src/world/map.rs index b1827b3f..fd1e1226 100644 --- a/minecraft-server/src/world/map.rs +++ b/minecraft-server/src/world/map.rs @@ -457,16 +457,29 @@ impl WorldMap { inner_get_block(self, position).await.unwrap_or(BlockWithState::Air) } - pub async fn get_network_chunk(&self, position: ChunkPosition) -> Option { - let chunk_column_position = position.chunk_column(); - let shard = chunk_column_position.shard(self.shard_count); - let cy_in_vec: usize = position.cy.saturating_add(4).try_into().ok()?; - + pub async fn get_network_chunk_column_data<'a>(&self, position: ChunkColumnPosition) -> Option> { + let shard = position.shard(self.shard_count); let shard = self.shards[shard].read().await; - let chunk_column = shard.get(&chunk_column_position)?; - let chunk = chunk_column.chunks.get(cy_in_vec)?; - - Some(chunk.as_network_chunk().clone()) + let chunk_column = shard.get(&position)?; + + let serialized = NetworkChunk::into_data(chunk_column.chunks.iter().map(|c| c.data.clone()).collect()).unwrap(); + let (skylight_array_data, skylight_mask, empty_skylight_mask) = chunk_column.light.get_packet(); + + let chunk_data = PlayClientbound::ChunkData { value: NetworkChunkColumnData { + chunk_x: position.cx, + chunk_z: position.cz, + heightmaps: chunk_column.heightmap.to_tag(), + data: Array::from(serialized.clone()), + block_entities: Array::default(), + sky_light_mask: skylight_mask, + block_light_mask: Array::default(), + empty_sky_light_mask: empty_skylight_mask, + empty_block_light_mask: Array::default(), + sky_light: skylight_array_data, + block_light: Array::default(), + }}; + let serialized = chunk_data.serialize_minecraft_packet().ok()?; + Some(serialized) } pub async fn set_block(&self, position: BlockPosition, block: BlockWithState) { diff --git a/minecraft-server/src/world/mod.rs b/minecraft-server/src/world/mod.rs index 081614c1..30e91dca 100644 --- a/minecraft-server/src/world/mod.rs +++ b/minecraft-server/src/world/mod.rs @@ -33,8 +33,8 @@ impl World { Some(self.map.get_block(position).await) } - pub async fn get_network_chunk(&self, position: ChunkPosition) -> Option { - self.map.get_network_chunk(position).await + pub async fn get_network_chunk_column_data<'a>(&self, position: ChunkColumnPosition) -> Option> { + self.map.get_network_chunk_column_data(position).await } pub async fn set_block(&self, position: BlockPosition, block: BlockWithState) { From aa5b00296df40fdf7af901d4442a58deb937eda5 Mon Sep 17 00:00:00 2001 From: DimitriTimoz Date: Sat, 18 Nov 2023 17:40:50 +0100 Subject: [PATCH 31/46] Fix clear light from --- minecraft-server/src/world/light.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/minecraft-server/src/world/light.rs b/minecraft-server/src/world/light.rs index 013223dd..ca7e2043 100644 --- a/minecraft-server/src/world/light.rs +++ b/minecraft-server/src/world/light.rs @@ -369,7 +369,7 @@ impl ChunkColumn { while let Some(position) = to_explore.pop() { let neighbors = position.get_neighbors(self.light.sky_light.light_arrays.len()); let my_level = self.light.sky_light.get_level(position.clone())?; - let my_is_inside = false; // get it + let my_is_inside = self.get_highest_block_at(&position.clone().into()) + 16 > position.y as u16 + 1; for neighbor in neighbors { let neighbor_level = self.light.sky_light.get_level(neighbor.clone()).unwrap(); From ead4294842f60d87caa983d0f22234195ef101ce Mon Sep 17 00:00:00 2001 From: DimitriTimoz Date: Sat, 18 Nov 2023 21:27:48 +0100 Subject: [PATCH 32/46] backpropagation of light to edit --- minecraft-positions/src/lib.rs | 13 +++ minecraft-server/src/world/light.rs | 132 ++++++++++++++++++++++------ minecraft-server/src/world/map.rs | 33 +++++-- minecraft-server/src/world/mod.rs | 1 - 4 files changed, 142 insertions(+), 37 deletions(-) diff --git a/minecraft-positions/src/lib.rs b/minecraft-positions/src/lib.rs index 3d3f0ce8..5fd3b5fa 100644 --- a/minecraft-positions/src/lib.rs +++ b/minecraft-positions/src/lib.rs @@ -1,5 +1,7 @@ mod shards; +use std::ops::{AddAssign, Add}; + pub use minecraft_protocol::packets::Position as NetworkPosition; #[derive(Debug, Clone, PartialEq, Eq, Hash, Default)] @@ -168,6 +170,17 @@ pub struct ChunkColumnPosition { pub cz: i32, } +impl Add for ChunkColumnPosition { + type Output = ChunkColumnPosition; + + fn add(self, rhs: ChunkColumnPosition) -> Self::Output { + ChunkColumnPosition { + cx: self.cx + rhs.cx, + cz: self.cz + rhs.cz, + } + } +} + impl ChunkColumnPosition { pub fn chunk(&self, cy: i32) -> ChunkPosition { ChunkPosition { diff --git a/minecraft-server/src/world/light.rs b/minecraft-server/src/world/light.rs index ca7e2043..eeb3a2f7 100644 --- a/minecraft-server/src/world/light.rs +++ b/minecraft-server/src/world/light.rs @@ -81,6 +81,63 @@ impl SectionLightData { } } +#[derive(Debug, Clone)] +pub struct EdgesLightToPropagate { + pub edges: [BinaryHeap<(LightPositionInChunkColumn, u8)>; 4], +} + +impl EdgesLightToPropagate { + pub fn new() -> Self { + Self { + edges: [BinaryHeap::new(), BinaryHeap::new(), BinaryHeap::new(), BinaryHeap::new()], + } + } + + pub fn push(&mut self, position: LightPositionInChunkColumn, level: u8) { + let index = match position { + LightPositionInChunkColumn { bx: 0, y: _, bz: _ } => 0, + LightPositionInChunkColumn { bx: _, y: _, bz: 0 } => 1, + LightPositionInChunkColumn { bx: 15, y: _, bz: _ } => 2, + LightPositionInChunkColumn { bx: _, y: _, bz: 15 } => 3, + _ => return, + }; + self.edges[index].push((position, level)); + } + + pub fn pop(&mut self) -> Option<(LightPositionInChunkColumn, u8)> { + for edge in self.edges.iter_mut() { + if let Some((position, level)) = edge.pop() { + return Some((position, level)); + } + } + None + } + + pub fn expand(&mut self, edges: EdgesLightToPropagate) { + for (i, edge) in edges.edges.iter().enumerate() { + self.edges[i].extend(edge.clone()); + } + } + + pub fn chunk_positions_to_propagate(&self, from: ChunkColumnPosition) -> Vec<(ChunkColumnPosition, BinaryHeap<(LightPositionInChunkColumn, u8)>)> { + let mut result = Vec::new(); + if !self.edges[0].is_empty() { + result.push((from.clone() + ChunkColumnPosition { cx: -1, cz: 0 }, self.edges[0].clone())); + } + if !self.edges[1].is_empty() { + result.push((from.clone() + ChunkColumnPosition { cx: 0, cz: -1 }, self.edges[1].clone())); + } + if !self.edges[2].is_empty() { + result.push((from.clone() + ChunkColumnPosition { cx: 1, cz: 0 }, self.edges[2].clone())); + } + if !self.edges[3].is_empty() { + result.push((from.clone() + ChunkColumnPosition { cx: 0, cz: 1 }, self.edges[3].clone())); + } + + result + } +} + #[derive(Debug, Clone)] struct LightSystem { /// The level of the sky light, 15 is the maximum. @@ -91,7 +148,7 @@ struct LightSystem { pub light_mask: u64, /// The mask of sections that don't have sky light data. pub empty_light_mask: u64, - zero_chunk_index: usize, + edge_light_to_propagate: EdgesLightToPropagate, } impl LightSystem { @@ -113,7 +170,7 @@ impl LightSystem { /// Get the light mask and the empty light mask as bitsets. /// return (light_mask, empty_light_mask) fn masks_to_bitset<'a>(&self) -> (BitSet<'a>, BitSet<'a>) { - let mut light_mask = BitSet::from(vec![self.light_mask as i64]); + let light_mask = BitSet::from(vec![self.light_mask as i64]); let empty_light_mask = BitSet::from(vec![self.empty_light_mask as i64]); (light_mask, empty_light_mask) } @@ -127,7 +184,7 @@ impl LightSystem { } /// Set the sky light in the given section. - pub fn set_region(&mut self, from_y: usize, to_y: usize, level: u8) -> Result<(), ()> { + pub fn set_region(&mut self, from_y: usize, to_y: usize, level: u8) -> Result { if level > self.level { return Err(()); } @@ -139,17 +196,32 @@ impl LightSystem { let last_section = to_y.div_euclid(16); let last_section_offset = to_y.rem_euclid(16); - println!("Setting sky light from {} to {} in sections {} to {}", from_y, to_y, first_section, last_section); + let mut edges = EdgesLightToPropagate::new(); + for section in first_section..=last_section { if section != first_section && section != last_section { // Set the whole section self.light_arrays[section].set_with(level); + for y in 0..16 { + for i in 0..16 { + edges.push(LightPositionInChunkColumn { bx: i, y: section * 16 + y, bz: 0 }, level); + edges.push(LightPositionInChunkColumn { bx: i, y: section * 16 + y, bz: 15 }, level); + edges.push(LightPositionInChunkColumn { bx: 0, y: section * 16 + y, bz: i }, level); + edges.push(LightPositionInChunkColumn { bx: 15, y: section * 16 + y, bz: i }, level); + } + } } else { // Set the part of the section let first_offset = if section == first_section { first_secion_offset } else { 0 }; let last_offset = if section == last_section { last_section_offset } else { 15 }; for y in first_offset..=last_offset { self.light_arrays[section].set_layer(y as u8, level)?; + for i in 0..16 { + edges.push(LightPositionInChunkColumn { bx: i, y: section * 16 + y, bz: 0 }, level); + edges.push(LightPositionInChunkColumn { bx: i, y: section * 16 + y, bz: 15 }, level); + edges.push(LightPositionInChunkColumn { bx: 0, y: section * 16 + y, bz: i }, level); + edges.push(LightPositionInChunkColumn { bx: 15, y: section * 16 + y, bz: i }, level); + } } } @@ -164,7 +236,7 @@ impl LightSystem { } } - Ok(()) + Ok(edges) } pub(super) fn get_level(&self, position: LightPositionInChunkColumn) -> Result { @@ -172,7 +244,7 @@ impl LightSystem { self.light_arrays[section.max(0)].get(position.in_chunk()) } - pub(super) fn set_level(&mut self, position: LightPositionInChunkColumn, level: u8) -> Result<(), ()> { + pub(super) fn set_level(&mut self, position: LightPositionInChunkColumn, level: u8) -> Result { let section = position.y.div_euclid(16); // Update the mask let mask = 1 << section; @@ -184,7 +256,10 @@ impl LightSystem { self.empty_light_mask |= mask; self.light_mask &= !mask; } - self.light_arrays[section.max(0)].set(position.in_chunk(), level) + self.light_arrays[section.max(0)].set(position.in_chunk(), level)?; + let mut edges = EdgesLightToPropagate::new(); + edges.push(position, level); + Ok(edges) } } @@ -201,7 +276,7 @@ impl Light { light_arrays: vec![SectionLightData::new(); 24+2], light_mask: 0, empty_light_mask: !0, - zero_chunk_index: 4, // We start at y=-64, and we have a chunk under that. + edge_light_to_propagate: EdgesLightToPropagate::new(), }, } } @@ -213,7 +288,7 @@ impl Light { } #[derive(Debug, Clone)] -struct LightPositionInChunkColumn { +pub struct LightPositionInChunkColumn { pub bx: u8, pub y: usize, pub bz: u8, @@ -299,17 +374,18 @@ impl std::cmp::Ord for LightPositionInChunkColumn { } impl ChunkColumn { - pub(super) fn init_light(&mut self) -> Result<(), ()> { + pub(super) fn init_light(&mut self) -> Result { self.propagate_sky_light_inside() } - fn propagate_sky_light_inside(&mut self) -> Result<(), ()> { + fn propagate_sky_light_inside(&mut self) -> Result { + let mut to_propagate = EdgesLightToPropagate::new(); // Set all highest blocks to the highest block let highest_blocks = self.get_highest_block(); let max_y = self.light.sky_light.light_arrays.len() * 16 - 1; - self.light.sky_light.set_region(highest_blocks as usize + 16, max_y, self.light.sky_light.level)?; let mut to_explore: BinaryHeap = BinaryHeap::new(); + to_propagate.expand(self.light.sky_light.set_region(highest_blocks as usize + 16, max_y, self.light.sky_light.level)?); // Add all highest blocks to the queue for x in 0..16 { @@ -323,16 +399,15 @@ impl ChunkColumn { } } - self.explore_sky_light_from_heap(&mut to_explore).map_err(|_| error!("Error while updating light")).unwrap(); - - Ok(()) + to_propagate.expand(self.explore_sky_light_from_heap(&mut to_explore).map_err(|_| error!("Error while updating light"))?); + Ok(to_propagate) } - fn explore_sky_light_from_heap(&mut self, to_explore: &mut BinaryHeap) -> Result<(), ()> { + fn explore_sky_light_from_heap(&mut self, to_explore: &mut BinaryHeap) -> Result { // We get the neighbors and determine the light source from them // The neighbor with the highest light level is the light source // So we explore from it - + let mut edges = EdgesLightToPropagate::new(); while let Some(position) = to_explore.pop() { let neighbors = position.get_neighbors(self.light.sky_light.light_arrays.len()); let my_level = self.light.sky_light.get_level(position.clone())?; @@ -348,15 +423,16 @@ impl ChunkColumn { let is_inside = highest_block > neighbor.y as u16 + 1; to_explore.push(neighbor.clone()); let new_level = if is_inside { my_level - 1 } else { self.light.sky_light.level }; - self.light.sky_light.set_level(neighbor, new_level)?; + edges.expand(self.light.sky_light.set_level(neighbor, new_level)?); } } } - Ok(()) + Ok(edges) } - fn clear_lightsystem_from(&mut self, position: LightPositionInChunkColumn) -> Result<(), ()> { + fn clear_lightsystem_from(&mut self, position: LightPositionInChunkColumn) -> Result { let mut to_explore: BinaryHeap = BinaryHeap::new(); + let mut edges = EdgesLightToPropagate::new(); // We get the neighbors and determine the light source from them // The neighbor with the highest light level is the light source // then we clear from the other neighbors @@ -384,21 +460,23 @@ impl ChunkColumn { let is_inside = highest_block > neighbor.y as u16 + 1; to_explore.push(neighbor.clone()); let new_level = if is_inside { my_level - block.light_absorption() - 1 } else { self.light.sky_light.level }; - self.light.sky_light.set_level(neighbor, new_level)?; + edges.expand(self.light.sky_light.set_level(neighbor, new_level)?); } } } - Ok(()) + Ok(edges) } - pub(super) fn update_light_as_block_changed_at(&mut self, position: BlockPositionInChunkColumn, blocking: bool) { + pub(super) fn update_light_as_block_changed_at(&mut self, position: BlockPositionInChunkColumn) -> Result { let position = LightPositionInChunkColumn::from(position); - + let blocking = !Block::from(self.get_block(position.clone().into())).is_transparent(); let mut to_explore: BinaryHeap = BinaryHeap::from(position.get_neighbors(self.light.sky_light.light_arrays.len())); + let mut to_propagate = EdgesLightToPropagate::new(); if blocking { - let _ = self.clear_lightsystem_from(position.clone()).map_err(|_| error!("Error while updating light")); + to_propagate.expand(self.clear_lightsystem_from(position.clone()).map_err(|_| error!("Error while updating light"))?); } - let _ = self.explore_sky_light_from_heap(&mut to_explore).map_err(|_| error!("Error while updating light")); + to_propagate.expand(self.explore_sky_light_from_heap(&mut to_explore).map_err(|_| error!("Error while updating light"))?); + Ok(to_propagate) } } @@ -457,7 +535,7 @@ mod tests { light_arrays: vec![SectionLightData::new(); 16+2], light_mask: 0, empty_light_mask: !0, - zero_chunk_index: 4, // We start at y=-64, and we have a chunk under that. + edge_light_to_propagate: EdgesLightToPropagate::new(), }; sky_light.set_region(1, 33, 15).unwrap(); diff --git a/minecraft-server/src/world/map.rs b/minecraft-server/src/world/map.rs index fd1e1226..5ea90242 100644 --- a/minecraft-server/src/world/map.rs +++ b/minecraft-server/src/world/map.rs @@ -1,8 +1,8 @@ -use std::{collections::HashMap, cmp::Ordering, vec}; +use std::{collections::{HashMap, BinaryHeap}, cmp::Ordering, vec}; use minecraft_protocol::{components::chunk::PalettedData, ids::blocks::Block}; use tokio::sync::RwLock; -use crate::prelude::*; -use super::light::Light; +use crate::{prelude::*, world::light::EdgesLightToPropagate}; +use super::light::{Light, LightPositionInChunkColumn}; pub struct WorldMap { /// The map is divided in shards. @@ -429,8 +429,6 @@ impl ChunkColumn { }, _ => {} } - trace!("setting"); - self.update_light_as_block_changed_at(position, !not_filter_sunlight); } } @@ -457,7 +455,7 @@ impl WorldMap { inner_get_block(self, position).await.unwrap_or(BlockWithState::Air) } - pub async fn get_network_chunk_column_data<'a>(&self, position: ChunkColumnPosition) -> Option> { + pub async fn get_network_chunk_column_data(&self, position: ChunkColumnPosition) -> Option> { let shard = position.shard(self.shard_count); let shard = self.shards[shard].read().await; let chunk_column = shard.get(&position)?; @@ -483,7 +481,7 @@ impl WorldMap { } pub async fn set_block(&self, position: BlockPosition, block: BlockWithState) { - async fn inner_get_block(s: &WorldMap, position: BlockPosition, block: BlockWithState) -> Option<()> { + async fn inner_get_block(s: &WorldMap, position: BlockPosition, block: BlockWithState) -> Option<(EdgesLightToPropagate, ChunkColumnPosition)> { let chunk_position = position.chunk(); let position_in_chunk_column = position.in_chunk_column(); let chunk_column_position = chunk_position.chunk_column(); @@ -492,9 +490,26 @@ impl WorldMap { let mut shard = s.shards[shard].write().await; let chunk_column = shard.get_mut(&chunk_column_position)?; chunk_column.set_block(position_in_chunk_column.clone(), block); - Some(()) + chunk_column.update_light_as_block_changed_at(position_in_chunk_column).ok().map(|to_propagate| (to_propagate, chunk_column_position)) + } - inner_get_block(self, position, block).await; + let to_propagate = inner_get_block(self, position, block).await; + if let Some(to_propagate) = to_propagate { + let (to_propagate, from) = to_propagate; + let to_popagate = to_propagate.chunk_positions_to_propagate(from); + for (chunk_column_position, to_propagate) in to_popagate { + self.update_light_from_edge(self, chunk_column_position, to_propagate).await; + } + } + } + + async fn update_light_from_edge(&self, s: &WorldMap, chunk_column_position: ChunkColumnPosition, to_propagate: BinaryHeap<(LightPositionInChunkColumn, u8)>) { + let shard = chunk_column_position.shard(self.shard_count); + + let mut shard = s.shards[shard].write().await; + let chunk_column = shard.get_mut(&chunk_column_position); + + } pub async fn load(&self, position: ChunkColumnPosition) { diff --git a/minecraft-server/src/world/mod.rs b/minecraft-server/src/world/mod.rs index 30e91dca..04e582ec 100644 --- a/minecraft-server/src/world/mod.rs +++ b/minecraft-server/src/world/mod.rs @@ -14,7 +14,6 @@ use light::*; pub struct World { map: WorldMap, entities: Entities, - loading_manager: RwLock, change_senders: RwLock>>, // TODO: Add a way to select events you want to subscribe to } From 2bebed7949d9b24686335298d5efcef29f0dd2b8 Mon Sep 17 00:00:00 2001 From: DimitriTimoz Date: Sat, 18 Nov 2023 23:15:26 +0100 Subject: [PATCH 33/46] Refactor sky light calculations and add unit tests --- minecraft-server/src/world/light.rs | 35 ++-------------------- minecraft-server/src/world/map.rs | 45 +++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 32 deletions(-) diff --git a/minecraft-server/src/world/light.rs b/minecraft-server/src/world/light.rs index eeb3a2f7..a8da106e 100644 --- a/minecraft-server/src/world/light.rs +++ b/minecraft-server/src/world/light.rs @@ -285,6 +285,9 @@ impl Light { self.sky_light.get_packet_data() } + pub fn get_skylight_level(&self, position: LightPositionInChunkColumn) -> u8 { + self.sky_light.get_level(position).unwrap_or_default() + } } #[derive(Debug, Clone)] @@ -551,36 +554,4 @@ mod tests { assert_eq!(sky_light.light_arrays[4].get(BlockPositionInChunk { bx: 9, by: 0, bz: 10 }).unwrap(), 0); } - #[test] - fn test_sky_light_flat_chunk() { - let mut flat_chunk = ChunkColumn::flat(); - - // Check that the sky light is equal to the light level above the grass and on the top of the world. - for x in 0..16 { - for z in 0..16 { - assert_eq!(flat_chunk.light.sky_light.light_arrays[0].get(BlockPositionInChunk { bx: x, by: 0, bz: z }).unwrap(), 0); - assert_eq!(flat_chunk.light.sky_light.light_arrays[4].get(BlockPositionInChunk { bx: x, by: 0, bz: z }).unwrap(), 15); - assert_eq!(flat_chunk.light.sky_light.light_arrays[25].get(BlockPositionInChunk { bx: x, by: 15, bz: z }).unwrap(), 15); - } - } - - // Break the grass block and check that the sky light is correct. - assert_eq!(flat_chunk.light.sky_light.light_arrays[1].get(BlockPositionInChunk { bx: 0, by: 15, bz: 0 }).unwrap(), 0); - flat_chunk.set_block_for_test(BlockPositionInChunkColumn { bx: 0, y: -49, bz: 0 }, Block::Air.into()); - assert_eq!(flat_chunk.light.sky_light.light_arrays[1].get(BlockPositionInChunk { bx: 0, by: 15, bz: 0 }).unwrap(), 15); - - assert_eq!(flat_chunk.light.sky_light.light_arrays[1].get(BlockPositionInChunk { bx: 0, by: 14, bz: 0 }).unwrap(), 0); - flat_chunk.set_block_for_test(BlockPositionInChunkColumn { bx: 0, y: -50, bz: 0 }, Block::Air.into()); - assert_eq!(flat_chunk.light.sky_light.light_arrays[1].get(BlockPositionInChunk { bx: 0, by: 14, bz: 0 }).unwrap(), 15); - - flat_chunk.set_block_for_test(BlockPositionInChunkColumn { bx: 0, y: -50, bz: 1 }, Block::Air.into()); - assert_eq!(flat_chunk.light.sky_light.light_arrays[1].get(BlockPositionInChunk { bx: 0, by: 14, bz: 1 }).unwrap(), 14); - assert_eq!(flat_chunk.light.sky_light.light_arrays[1].get(BlockPositionInChunk { bx: 0, by: 0, bz: 10 }).unwrap(), 0); - - flat_chunk.set_block_for_test(BlockPositionInChunkColumn { bx: 0, y: -50, bz: 2 }, Block::Air.into()); - assert_eq!(flat_chunk.light.sky_light.light_arrays[1].get(BlockPositionInChunk { bx: 0, by: 14, bz: 2 }).unwrap(), 13); - - flat_chunk.set_block_for_test(BlockPositionInChunkColumn { bx: 0, y: -51, bz: 2 }, Block::Air.into()); - assert_eq!(flat_chunk.light.sky_light.light_arrays[1].get(BlockPositionInChunk { bx: 0, by: 13, bz: 2 }).unwrap(), 12); - } } diff --git a/minecraft-server/src/world/map.rs b/minecraft-server/src/world/map.rs index 5ea90242..3c77f19c 100644 --- a/minecraft-server/src/world/map.rs +++ b/minecraft-server/src/world/map.rs @@ -430,6 +430,10 @@ impl ChunkColumn { _ => {} } } + + fn get_skylight(&self, position: BlockPositionInChunkColumn) -> u8 { + self.light.get_skylight_level(position.into()) + } } impl WorldMap { @@ -503,6 +507,17 @@ impl WorldMap { } } + pub async fn get_skylight(&self, position: BlockPosition) -> u8 { + let chunk_position = position.chunk(); + let position_in_chunk_column = position.in_chunk_column(); + let chunk_column_position = chunk_position.chunk_column(); + let shard = chunk_column_position.shard(self.shard_count); + + let shard = self.shards[shard].read().await; + let chunk_column = shard.get(&chunk_column_position).unwrap(); + chunk_column.light.get_skylight_level(position_in_chunk_column.into()) + } + async fn update_light_from_edge(&self, s: &WorldMap, chunk_column_position: ChunkColumnPosition, to_propagate: BinaryHeap<(LightPositionInChunkColumn, u8)>) { let shard = chunk_column_position.shard(self.shard_count); @@ -726,6 +741,36 @@ mod tests { } + #[tokio::test] + async fn test_sky_light_flat_chunk() { + let world = WorldMap::new(10); + world.load(ChunkColumnPosition { cx: 0, cz: 0 }).await; + + + // Check that the sky light is equal to the light level above the grass and on the top of the world. + for x in 0..16 { + for z in 0..16 { + assert_eq!(world.get_skylight(BlockPosition { x, y: -60, z}).await, 0); + assert_eq!(world.get_skylight(BlockPosition { x, y: -49, z}).await, 0); + assert_eq!(world.get_skylight(BlockPosition { x, y: 120, z}).await, 15); + } + } + + // Break the grass block and check that the sky light is correct. + assert_ne!(world.get_skylight(BlockPosition { x: 0, y: -49, z: 0}).await, 15); + world.set_block(BlockPosition { x: 0, y: -49, z: 0 }, BlockWithState::Air).await; + assert_eq!(world.get_skylight(BlockPosition { x: 0, y: -49, z: 0}).await, 15); + + assert_ne!(world.get_skylight(BlockPosition { x: 0, y: -50, z: 0}).await, 15); + world.set_block(BlockPosition { x: 0, y: -50, z: 0 }, BlockWithState::Air).await; + assert_eq!(world.get_skylight(BlockPosition { x: 0, y: -50, z: 0}).await, 15); + + assert_ne!(world.get_skylight(BlockPosition { x: 0, y: -50, z: 1}).await, 14); + world.set_block(BlockPosition { x: 0, y: -50, z: 1 }, BlockWithState::Air).await; + assert_eq!(world.get_skylight(BlockPosition { x: 0, y: -50, z: 1}).await, 14); + } + + #[test] fn benchmark_get_block() { From 7b45ce06f7f7c8ffb2e6c2500068371c9e5ee6ce Mon Sep 17 00:00:00 2001 From: DimitriTimoz Date: Sun, 19 Nov 2023 01:43:15 +0100 Subject: [PATCH 34/46] A bit closer --- minecraft-server/src/world/light.rs | 96 +++++++++++++++++------------ minecraft-server/src/world/map.rs | 62 +++++++++++-------- 2 files changed, 95 insertions(+), 63 deletions(-) diff --git a/minecraft-server/src/world/light.rs b/minecraft-server/src/world/light.rs index a8da106e..b1c4610a 100644 --- a/minecraft-server/src/world/light.rs +++ b/minecraft-server/src/world/light.rs @@ -5,6 +5,8 @@ use minecraft_protocol::ids::blocks::Block; use crate::prelude::*; use super::*; +const MAX_LIGHT_LEVEL: u8 = 15; + #[derive(Debug, Clone)] struct SectionLightData(Vec); // TODO(optimization): Use simd @@ -38,7 +40,7 @@ impl SectionLightData { /// Set the light level at the given position. pub fn set(&mut self, postion: BlockPositionInChunk, level: u8) -> Result<(), ()> { - if level > 15 { + if level > MAX_LIGHT_LEVEL { return Err(()); } @@ -61,11 +63,11 @@ impl SectionLightData { /// Set the light level at the given layer to the given level. pub(super) fn set_layer(&mut self, layer: u8 , level: u8) -> Result<(), ()> { - if level > 15 { + if level > MAX_LIGHT_LEVEL { return Err(()); } - if layer > 15 { + if layer > MAX_LIGHT_LEVEL { return Err(()); } @@ -93,32 +95,40 @@ impl EdgesLightToPropagate { } } + /// Push the given position and level to the correct edge. + /// If the position is not on an edge, nothing is done. + /// The position coordinate will be modified to be on the adjacent chunk pub fn push(&mut self, position: LightPositionInChunkColumn, level: u8) { + let mut position = position; let index = match position { - LightPositionInChunkColumn { bx: 0, y: _, bz: _ } => 0, - LightPositionInChunkColumn { bx: _, y: _, bz: 0 } => 1, - LightPositionInChunkColumn { bx: 15, y: _, bz: _ } => 2, - LightPositionInChunkColumn { bx: _, y: _, bz: 15 } => 3, + LightPositionInChunkColumn { bx: 0, y: _, bz: _ } => { + position.bx = 15; + 0 + }, + LightPositionInChunkColumn { bx: _, y: _, bz: 0 } => { + position.bz = 15; + 1 + } + LightPositionInChunkColumn { bx: 15, y: _, bz: _ } => { + position.bx = 0; + 2 + } + LightPositionInChunkColumn { bx: _, y: _, bz: 15 } => { + position.bz = 0; + 3 + } _ => return, }; self.edges[index].push((position, level)); } - pub fn pop(&mut self) -> Option<(LightPositionInChunkColumn, u8)> { - for edge in self.edges.iter_mut() { - if let Some((position, level)) = edge.pop() { - return Some((position, level)); - } - } - None - } - pub fn expand(&mut self, edges: EdgesLightToPropagate) { for (i, edge) in edges.edges.iter().enumerate() { self.edges[i].extend(edge.clone()); } } - + + /// Get the ChunkColumnPositions of chunks that need to be propagated pub fn chunk_positions_to_propagate(&self, from: ChunkColumnPosition) -> Vec<(ChunkColumnPosition, BinaryHeap<(LightPositionInChunkColumn, u8)>)> { let mut result = Vec::new(); if !self.edges[0].is_empty() { @@ -148,7 +158,6 @@ struct LightSystem { pub light_mask: u64, /// The mask of sections that don't have sky light data. pub empty_light_mask: u64, - edge_light_to_propagate: EdgesLightToPropagate, } impl LightSystem { @@ -205,22 +214,22 @@ impl LightSystem { for y in 0..16 { for i in 0..16 { edges.push(LightPositionInChunkColumn { bx: i, y: section * 16 + y, bz: 0 }, level); - edges.push(LightPositionInChunkColumn { bx: i, y: section * 16 + y, bz: 15 }, level); + edges.push(LightPositionInChunkColumn { bx: i, y: section * 16 + y, bz: MAX_LIGHT_LEVEL }, level); edges.push(LightPositionInChunkColumn { bx: 0, y: section * 16 + y, bz: i }, level); - edges.push(LightPositionInChunkColumn { bx: 15, y: section * 16 + y, bz: i }, level); + edges.push(LightPositionInChunkColumn { bx: MAX_LIGHT_LEVEL, y: section * 16 + y, bz: i }, level); } } } else { // Set the part of the section let first_offset = if section == first_section { first_secion_offset } else { 0 }; - let last_offset = if section == last_section { last_section_offset } else { 15 }; + let last_offset = if section == last_section { last_section_offset } else { MAX_LIGHT_LEVEL as usize }; for y in first_offset..=last_offset { self.light_arrays[section].set_layer(y as u8, level)?; for i in 0..16 { edges.push(LightPositionInChunkColumn { bx: i, y: section * 16 + y, bz: 0 }, level); - edges.push(LightPositionInChunkColumn { bx: i, y: section * 16 + y, bz: 15 }, level); + edges.push(LightPositionInChunkColumn { bx: i, y: section * 16 + y, bz: MAX_LIGHT_LEVEL }, level); edges.push(LightPositionInChunkColumn { bx: 0, y: section * 16 + y, bz: i }, level); - edges.push(LightPositionInChunkColumn { bx: 15, y: section * 16 + y, bz: i }, level); + edges.push(LightPositionInChunkColumn { bx: MAX_LIGHT_LEVEL, y: section * 16 + y, bz: i }, level); } } } @@ -272,11 +281,10 @@ impl Light { // TODO: Make this configurable with the world. Self { sky_light: LightSystem { - level: 15, + level: MAX_LIGHT_LEVEL, light_arrays: vec![SectionLightData::new(); 24+2], light_mask: 0, empty_light_mask: !0, - edge_light_to_propagate: EdgesLightToPropagate::new(), }, } } @@ -481,6 +489,17 @@ impl ChunkColumn { to_propagate.expand(self.explore_sky_light_from_heap(&mut to_explore).map_err(|_| error!("Error while updating light"))?); Ok(to_propagate) } + + pub(super) fn update_from_edge(&mut self, to_propagate: BinaryHeap<(LightPositionInChunkColumn, u8)>) -> Result<(), ()> { + for (position, level) in to_propagate { + let block = Block::from(self.get_block(position.clone().into())); + if block.is_transparent() { + self.light.sky_light.set_level(position.clone(), level.saturating_sub(block.light_absorption()))?; + self.update_light_as_block_changed_at(position.into())?; + } + } + Ok(()) + } } #[cfg(test)] @@ -491,8 +510,8 @@ mod tests { fn test_section_light_data() { let mut data = SectionLightData::new(); - data.set(BlockPositionInChunk { bx: 0, by: 0, bz: 0 }, 15).unwrap(); - assert_eq!(data.get(BlockPositionInChunk { bx: 0, by: 0, bz: 0 }).unwrap(), 15); + data.set(BlockPositionInChunk { bx: 0, by: 0, bz: 0 }, MAX_LIGHT_LEVEL).unwrap(); + assert_eq!(data.get(BlockPositionInChunk { bx: 0, by: 0, bz: 0 }).unwrap(), MAX_LIGHT_LEVEL); data.set(BlockPositionInChunk { bx: 0, by: 0, bz: 0 }, 0).unwrap(); assert_eq!(data.get(BlockPositionInChunk { bx: 0, by: 0, bz: 0 }).unwrap(), 0); @@ -500,8 +519,8 @@ mod tests { data.set(BlockPositionInChunk { bx: 0, by: 0, bz: 1 }, 1).unwrap(); assert_eq!(data.get(BlockPositionInChunk { bx: 0, by: 0, bz: 1 }).unwrap(), 1); - data.set(BlockPositionInChunk { bx: 0, by: 1, bz: 1 }, 15).unwrap(); - assert_eq!(data.get(BlockPositionInChunk { bx: 0, by: 1, bz: 1 }).unwrap(), 15); + data.set(BlockPositionInChunk { bx: 0, by: 1, bz: 1 }, MAX_LIGHT_LEVEL).unwrap(); + assert_eq!(data.get(BlockPositionInChunk { bx: 0, by: 1, bz: 1 }).unwrap(), MAX_LIGHT_LEVEL); data.set(BlockPositionInChunk { bx: 1, by: 1, bz: 1 }, 1).unwrap(); assert_eq!(data.get(BlockPositionInChunk { bx: 1, by: 1, bz: 1 }).unwrap(), 1); @@ -512,21 +531,21 @@ mod tests { // Manual layer for z in 0..16 { for x in 0..16 { - data.set(BlockPositionInChunk { bx: x, by: 0, bz: z }, 15).unwrap(); + data.set(BlockPositionInChunk { bx: x, by: 0, bz: z }, MAX_LIGHT_LEVEL).unwrap(); } } for z in 0..16 { for x in 0..16 { - assert_eq!(data.get(BlockPositionInChunk { bx: x, by: 0, bz: z }).unwrap(), 15, "x: {}, z: {}", x, z); + assert_eq!(data.get(BlockPositionInChunk { bx: x, by: 0, bz: z }).unwrap(), MAX_LIGHT_LEVEL, "x: {}, z: {}", x, z); } } // Test layer - data.set_layer(1, 15).unwrap(); + data.set_layer(1, MAX_LIGHT_LEVEL).unwrap(); for x in 0..16 { for z in 0..16 { - assert_eq!(data.get(BlockPositionInChunk { bx: x, by: 1, bz: z }).unwrap(), 15, "x: {}, z: {}", x, z); + assert_eq!(data.get(BlockPositionInChunk { bx: x, by: 1, bz: z }).unwrap(), MAX_LIGHT_LEVEL, "x: {}, z: {}", x, z); } } } @@ -534,19 +553,18 @@ mod tests { #[test] fn test_set_region() { let mut sky_light = LightSystem { - level: 15, + level: MAX_LIGHT_LEVEL, light_arrays: vec![SectionLightData::new(); 16+2], light_mask: 0, empty_light_mask: !0, - edge_light_to_propagate: EdgesLightToPropagate::new(), }; - sky_light.set_region(1, 33, 15).unwrap(); + sky_light.set_region(1, 33, MAX_LIGHT_LEVEL).unwrap(); // Test in - assert_eq!(sky_light.light_arrays[0].get(BlockPositionInChunk { bx: 0, by: 1, bz: 7 }).unwrap(), 15); - assert_eq!(sky_light.light_arrays[1].get(BlockPositionInChunk { bx: 1, by: 15, bz: 8 }).unwrap(), 15); - assert_eq!(sky_light.light_arrays[2].get(BlockPositionInChunk { bx: 3, by: 0, bz: 0 }).unwrap(), 15); + assert_eq!(sky_light.light_arrays[0].get(BlockPositionInChunk { bx: 0, by: 1, bz: 7 }).unwrap(), MAX_LIGHT_LEVEL); + assert_eq!(sky_light.light_arrays[1].get(BlockPositionInChunk { bx: 1, by: MAX_LIGHT_LEVEL, bz: 8 }).unwrap(), MAX_LIGHT_LEVEL); + assert_eq!(sky_light.light_arrays[2].get(BlockPositionInChunk { bx: 3, by: 0, bz: 0 }).unwrap(), MAX_LIGHT_LEVEL); // Test out assert_eq!(sky_light.light_arrays[0].get(BlockPositionInChunk { bx: 4, by: 0, bz: 2 }).unwrap(), 0); diff --git a/minecraft-server/src/world/map.rs b/minecraft-server/src/world/map.rs index 3c77f19c..7b2283b1 100644 --- a/minecraft-server/src/world/map.rs +++ b/minecraft-server/src/world/map.rs @@ -341,7 +341,7 @@ impl ChunkColumn { } current_height } - + pub(super) fn get_highest_block(&self) -> u32 { self.heightmap.max_height.unwrap_or(0) } @@ -448,7 +448,7 @@ impl WorldMap { pub async fn get_block(&self, position: BlockPosition) -> BlockWithState { async fn inner_get_block(s: &WorldMap, position: BlockPosition) -> Option { let chunk_position = position.chunk(); - let position_in_chunk_column = position.in_chunk_column(); + let position_in_chunk_column: BlockPositionInChunkColumn = position.in_chunk_column(); let chunk_column_position = chunk_position.chunk_column(); let shard = chunk_column_position.shard(s.shard_count); @@ -485,7 +485,7 @@ impl WorldMap { } pub async fn set_block(&self, position: BlockPosition, block: BlockWithState) { - async fn inner_get_block(s: &WorldMap, position: BlockPosition, block: BlockWithState) -> Option<(EdgesLightToPropagate, ChunkColumnPosition)> { + async fn inner_set_block(s: &WorldMap, position: BlockPosition, block: BlockWithState) -> Option<(EdgesLightToPropagate, ChunkColumnPosition)> { let chunk_position = position.chunk(); let position_in_chunk_column = position.in_chunk_column(); let chunk_column_position = chunk_position.chunk_column(); @@ -495,36 +495,42 @@ impl WorldMap { let chunk_column = shard.get_mut(&chunk_column_position)?; chunk_column.set_block(position_in_chunk_column.clone(), block); chunk_column.update_light_as_block_changed_at(position_in_chunk_column).ok().map(|to_propagate| (to_propagate, chunk_column_position)) - } - let to_propagate = inner_get_block(self, position, block).await; + + let to_propagate = inner_set_block(self, position, block).await; if let Some(to_propagate) = to_propagate { let (to_propagate, from) = to_propagate; let to_popagate = to_propagate.chunk_positions_to_propagate(from); for (chunk_column_position, to_propagate) in to_popagate { - self.update_light_from_edge(self, chunk_column_position, to_propagate).await; + self.update_light_from_edge(chunk_column_position, to_propagate).await; } } } pub async fn get_skylight(&self, position: BlockPosition) -> u8 { - let chunk_position = position.chunk(); - let position_in_chunk_column = position.in_chunk_column(); - let chunk_column_position = chunk_position.chunk_column(); - let shard = chunk_column_position.shard(self.shard_count); - - let shard = self.shards[shard].read().await; - let chunk_column = shard.get(&chunk_column_position).unwrap(); - chunk_column.light.get_skylight_level(position_in_chunk_column.into()) + async fn inner_get_skylight(s: &WorldMap, position: BlockPosition) -> Option { + let chunk_position = position.chunk(); + let chunk_column_position = chunk_position.chunk_column(); + let shard = chunk_column_position.shard(s.shard_count); + + let shard = s.shards[shard].read().await; + let chunk_column = shard.get(&chunk_column_position)?; + let level = chunk_column.get_skylight(position.in_chunk_column()); + Some(level) + } + inner_get_skylight(self, position).await.unwrap_or(0) } - async fn update_light_from_edge(&self, s: &WorldMap, chunk_column_position: ChunkColumnPosition, to_propagate: BinaryHeap<(LightPositionInChunkColumn, u8)>) { - let shard = chunk_column_position.shard(self.shard_count); - - let mut shard = s.shards[shard].write().await; - let chunk_column = shard.get_mut(&chunk_column_position); + async fn update_light_from_edge(&self, chunk_column_position: ChunkColumnPosition, to_propagate: BinaryHeap<(LightPositionInChunkColumn, u8)>) { + async fn inner_get_skylight(s: &WorldMap, chunk_column_position: ChunkColumnPosition, to_propagate: BinaryHeap<(LightPositionInChunkColumn, u8)>) -> Option<()> { + let shard = chunk_column_position.shard(s.shard_count); - + let mut shard = s.shards[shard].write().await; + let chunk_column = shard.get_mut(&chunk_column_position)?; + chunk_column.update_from_edge(to_propagate).ok()?; + Some(()) + } + inner_get_skylight(self, chunk_column_position, to_propagate).await; } pub async fn load(&self, position: ChunkColumnPosition) { @@ -743,10 +749,9 @@ mod tests { #[tokio::test] async fn test_sky_light_flat_chunk() { - let world = WorldMap::new(10); + let world = WorldMap::new(100); world.load(ChunkColumnPosition { cx: 0, cz: 0 }).await; - // Check that the sky light is equal to the light level above the grass and on the top of the world. for x in 0..16 { for z in 0..16 { @@ -768,15 +773,24 @@ mod tests { assert_ne!(world.get_skylight(BlockPosition { x: 0, y: -50, z: 1}).await, 14); world.set_block(BlockPosition { x: 0, y: -50, z: 1 }, BlockWithState::Air).await; assert_eq!(world.get_skylight(BlockPosition { x: 0, y: -50, z: 1}).await, 14); + + // test on chunk border + world.load(ChunkColumnPosition { cx: 1, cz: 0 }).await; + world.load(ChunkColumnPosition { cx: 0, cz: 1 }).await; + world.load(ChunkColumnPosition { cx: 1, cz: 1 }).await; + world.load(ChunkColumnPosition { cx: -1, cz: -1 }).await; + + assert_ne!(world.get_skylight(BlockPosition { x: 0, y: -50, z: -1}).await, 14); + world.set_block(BlockPosition { x: 0, y: -50, z: -1 }, BlockWithState::Air).await; + assert_eq!(world.get_skylight(BlockPosition { x: 0, y: -50, z: -1}).await, 14); } #[test] fn benchmark_get_block() { - let start_time = std::time::Instant::now(); for _ in 0..441 { - let mut column = ChunkColumn::flat(); + let _column = ChunkColumn::flat(); } let elapsed: Duration = start_time.elapsed(); From 7ec12c5f73e30ab1a0c814ac7c0c76116664ed29 Mon Sep 17 00:00:00 2001 From: DimitriTimoz Date: Sun, 19 Nov 2023 18:43:33 +0100 Subject: [PATCH 35/46] Back to clean code --- minecraft-server/src/entities/player.rs | 58 +++---- minecraft-server/src/world/light.rs | 203 ++++++++---------------- minecraft-server/src/world/map.rs | 103 ++++-------- minecraft-server/src/world/mod.rs | 3 +- 4 files changed, 115 insertions(+), 252 deletions(-) diff --git a/minecraft-server/src/entities/player.rs b/minecraft-server/src/entities/player.rs index acf209b8..0e10449d 100644 --- a/minecraft-server/src/entities/player.rs +++ b/minecraft-server/src/entities/player.rs @@ -115,10 +115,10 @@ impl Handler { if loaded_chunks_after == player.loaded_chunks { return (None, EntityChanges::nothing()) }; let mut newly_loaded_chunks: Vec<_> = loaded_chunks_after.difference(&player.loaded_chunks).cloned().collect(); let unloaded_chunks: Vec<_> = player.loaded_chunks.difference(&loaded_chunks_after).cloned().collect(); - for skipped in newly_loaded_chunks.iter().skip(50) { + for skipped in newly_loaded_chunks.iter().skip(2) { loaded_chunks_after.remove(skipped); } - newly_loaded_chunks.truncate(50); + newly_loaded_chunks.truncate(2); let uuid = player.info.uuid; player.loaded_chunks = loaded_chunks_after.clone(); (Some((loaded_chunks_after, newly_loaded_chunks, unloaded_chunks, uuid)), EntityChanges::other()) @@ -128,39 +128,17 @@ impl Handler { self.world.update_loaded_chunks(uuid, loaded_chunks_after).await; // Send the chunks to the client - let mut heightmaps = HashMap::new(); - heightmaps.insert(String::from("MOTION_BLOCKING"), NbtTag::LongArray(vec![0; 37])); - let heightmaps = NbtTag::Compound(heightmaps); + let mut chunks = Vec::new(); for newly_loaded_chunk in newly_loaded_chunks { - let mut column = Vec::new(); - for cy in -4..20 { - let chunk = self.world.get_network_chunk(newly_loaded_chunk.chunk(cy)).await.unwrap_or_else(|| { - error!("Chunk not loaded: {newly_loaded_chunk:?}"); - NetworkChunk { // TODO hard error - block_count: 0, - blocks: PalettedData::Single { value: 0 }, - biomes: PalettedData::Single { value: 4 }, - } - }); - column.push(chunk); - } - let serialized: Vec = NetworkChunk::into_data(column).unwrap(); - let chunk_data = PlayClientbound::ChunkData { - value: ChunkData { - chunk_x: newly_loaded_chunk.cx, - chunk_z: newly_loaded_chunk.cz, - heightmaps: heightmaps.clone(), - data: Array::from(serialized.clone()), - block_entities: Array::default(), - sky_light_mask: Array::default(), - block_light_mask: Array::default(), - empty_sky_light_mask: Array::default(), - empty_block_light_mask: Array::default(), - sky_light: Array::default(), - block_light: Array::default(), - } - }; - self.send_packet(chunk_data).await; + let chunk = self.world.get_network_chunk_column_data(newly_loaded_chunk.clone()).await.unwrap_or_else(|| { + error!("Chunk not loaded: {newly_loaded_chunk:?}"); + panic!("Chunk not loaded: {newly_loaded_chunk:?}"); + }); + chunks.push(chunk); + } + + for chunk in chunks { + self.send_raw_packet(chunk).await; } // Tell the client to unload chunks @@ -185,6 +163,18 @@ impl Handler { packet_sender.send(packet).await.unwrap(); } + async fn send_raw_packet(&self, packet: Vec) { + let packets_sent = self.mutate(|player| { + player.packets_sent += 1; + (player.packets_sent, EntityChanges::other()) + }).await.unwrap_or(0); + if packets_sent > 500 { + warn!("Many packets sent ({packets_sent})"); + } + let Some(packet_sender) = self.observe(|player| player.packet_sender.clone()).await else {return}; + packet_sender.send(packet).await.unwrap(); + } + async fn on_server_message(self, message: ServerMessage) { use ServerMessage::*; match message { diff --git a/minecraft-server/src/world/light.rs b/minecraft-server/src/world/light.rs index b1c4610a..c3894e2b 100644 --- a/minecraft-server/src/world/light.rs +++ b/minecraft-server/src/world/light.rs @@ -1,6 +1,4 @@ -use std::{collections::BinaryHeap, ops::AddAssign}; - -use minecraft_protocol::ids::blocks::Block; +use std::ops::AddAssign; use crate::prelude::*; use super::*; @@ -83,71 +81,6 @@ impl SectionLightData { } } -#[derive(Debug, Clone)] -pub struct EdgesLightToPropagate { - pub edges: [BinaryHeap<(LightPositionInChunkColumn, u8)>; 4], -} - -impl EdgesLightToPropagate { - pub fn new() -> Self { - Self { - edges: [BinaryHeap::new(), BinaryHeap::new(), BinaryHeap::new(), BinaryHeap::new()], - } - } - - /// Push the given position and level to the correct edge. - /// If the position is not on an edge, nothing is done. - /// The position coordinate will be modified to be on the adjacent chunk - pub fn push(&mut self, position: LightPositionInChunkColumn, level: u8) { - let mut position = position; - let index = match position { - LightPositionInChunkColumn { bx: 0, y: _, bz: _ } => { - position.bx = 15; - 0 - }, - LightPositionInChunkColumn { bx: _, y: _, bz: 0 } => { - position.bz = 15; - 1 - } - LightPositionInChunkColumn { bx: 15, y: _, bz: _ } => { - position.bx = 0; - 2 - } - LightPositionInChunkColumn { bx: _, y: _, bz: 15 } => { - position.bz = 0; - 3 - } - _ => return, - }; - self.edges[index].push((position, level)); - } - - pub fn expand(&mut self, edges: EdgesLightToPropagate) { - for (i, edge) in edges.edges.iter().enumerate() { - self.edges[i].extend(edge.clone()); - } - } - - /// Get the ChunkColumnPositions of chunks that need to be propagated - pub fn chunk_positions_to_propagate(&self, from: ChunkColumnPosition) -> Vec<(ChunkColumnPosition, BinaryHeap<(LightPositionInChunkColumn, u8)>)> { - let mut result = Vec::new(); - if !self.edges[0].is_empty() { - result.push((from.clone() + ChunkColumnPosition { cx: -1, cz: 0 }, self.edges[0].clone())); - } - if !self.edges[1].is_empty() { - result.push((from.clone() + ChunkColumnPosition { cx: 0, cz: -1 }, self.edges[1].clone())); - } - if !self.edges[2].is_empty() { - result.push((from.clone() + ChunkColumnPosition { cx: 1, cz: 0 }, self.edges[2].clone())); - } - if !self.edges[3].is_empty() { - result.push((from.clone() + ChunkColumnPosition { cx: 0, cz: 1 }, self.edges[3].clone())); - } - - result - } -} - #[derive(Debug, Clone)] struct LightSystem { /// The level of the sky light, 15 is the maximum. @@ -193,7 +126,7 @@ impl LightSystem { } /// Set the sky light in the given section. - pub fn set_region(&mut self, from_y: usize, to_y: usize, level: u8) -> Result { + pub fn set_region(&mut self, from_y: usize, to_y: usize, level: u8) -> Result<(), ()> { if level > self.level { return Err(()); } @@ -205,32 +138,17 @@ impl LightSystem { let last_section = to_y.div_euclid(16); let last_section_offset = to_y.rem_euclid(16); - let mut edges = EdgesLightToPropagate::new(); for section in first_section..=last_section { if section != first_section && section != last_section { // Set the whole section self.light_arrays[section].set_with(level); - for y in 0..16 { - for i in 0..16 { - edges.push(LightPositionInChunkColumn { bx: i, y: section * 16 + y, bz: 0 }, level); - edges.push(LightPositionInChunkColumn { bx: i, y: section * 16 + y, bz: MAX_LIGHT_LEVEL }, level); - edges.push(LightPositionInChunkColumn { bx: 0, y: section * 16 + y, bz: i }, level); - edges.push(LightPositionInChunkColumn { bx: MAX_LIGHT_LEVEL, y: section * 16 + y, bz: i }, level); - } - } } else { // Set the part of the section let first_offset = if section == first_section { first_secion_offset } else { 0 }; let last_offset = if section == last_section { last_section_offset } else { MAX_LIGHT_LEVEL as usize }; for y in first_offset..=last_offset { self.light_arrays[section].set_layer(y as u8, level)?; - for i in 0..16 { - edges.push(LightPositionInChunkColumn { bx: i, y: section * 16 + y, bz: 0 }, level); - edges.push(LightPositionInChunkColumn { bx: i, y: section * 16 + y, bz: MAX_LIGHT_LEVEL }, level); - edges.push(LightPositionInChunkColumn { bx: 0, y: section * 16 + y, bz: i }, level); - edges.push(LightPositionInChunkColumn { bx: MAX_LIGHT_LEVEL, y: section * 16 + y, bz: i }, level); - } } } @@ -245,7 +163,7 @@ impl LightSystem { } } - Ok(edges) + Ok(()) } pub(super) fn get_level(&self, position: LightPositionInChunkColumn) -> Result { @@ -253,7 +171,7 @@ impl LightSystem { self.light_arrays[section.max(0)].get(position.in_chunk()) } - pub(super) fn set_level(&mut self, position: LightPositionInChunkColumn, level: u8) -> Result { + pub(super) fn set_level(&mut self, position: LightPositionInChunkColumn, level: u8) -> Result<(), ()> { let section = position.y.div_euclid(16); // Update the mask let mask = 1 << section; @@ -266,9 +184,7 @@ impl LightSystem { self.light_mask &= !mask; } self.light_arrays[section.max(0)].set(position.in_chunk(), level)?; - let mut edges = EdgesLightToPropagate::new(); - edges.push(position, level); - Ok(edges) + Ok(()) } } @@ -313,83 +229,99 @@ impl LightPositionInChunkColumn { bz: self.bz, } } +} - pub fn get_neighbors(&self, n_chunk: usize) -> Vec { - let mut neighbors = Vec::new(); - if self.y < ((n_chunk - 1) * 16) + 1 { // No block can be higher so no block can affect the light level - neighbors.push(LightPositionInChunkColumn { bx: self.bx, y: self.y + 1, bz: self.bz }); - } - if self.bx > 0 { - neighbors.push(LightPositionInChunkColumn { bx: self.bx - 1, y: self.y, bz: self.bz }); + +impl From for LightPositionInChunkColumn { + fn from(val: BlockPositionInChunkColumn) -> Self { + Self { + bx: val.bx, + y: (val.y + 64 + 16) as usize, // TODO: Use the world config + bz: val.bz, } - if self.bx < 15 { - neighbors.push(LightPositionInChunkColumn { bx: self.bx + 1, y: self.y, bz: self.bz }); + } +} + +impl From for LightPositionInChunkColumn { + fn from(val: LightPosition) -> Self { + LightPositionInChunkColumn { + bx: val.x.rem_euclid(16) as u8, + y: val.y, + bz: val.z.rem_euclid(16) as u8, } - if self.bz > 0 { - neighbors.push(LightPositionInChunkColumn { bx: self.bx, y: self.y, bz: self.bz - 1 }); + } +} + +#[derive(Debug, Clone)] +pub struct LightPosition { + pub x: i32, + pub y: usize, + pub z: i32, +} + +impl LightPosition { + pub fn in_chunk(&self) -> BlockPositionInChunk { + BlockPositionInChunk { + bx: self.x.rem_euclid(16) as u8, + by: self.y.rem_euclid(16) as u8, + bz: self.z.rem_euclid(16) as u8, } - if self.bz < 15 { - neighbors.push(LightPositionInChunkColumn { bx: self.bx, y: self.y, bz: self.bz + 1 }); + } + + pub fn get_neighbors(&self, n_chunk: usize) -> Vec { + let mut neighbors = Vec::new(); + if self.y < ((n_chunk - 1) * 16) + 1 { // No block can be higher so no block can affect the light level + neighbors.push(LightPosition { x: self.x, y: self.y + 1, z: self.z }); } + neighbors.push(LightPosition { x: self.x - 1, y: self.y, z: self.z }); + neighbors.push(LightPosition { x: self.x + 1, y: self.y, z: self.z }); + neighbors.push(LightPosition { x: self.x, y: self.y, z: self.z - 1 }); + neighbors.push(LightPosition { x: self.x, y: self.y, z: self.z + 1 }); if self.y > 0 { - neighbors.push(LightPositionInChunkColumn { bx: self.bx, y: self.y - 1, bz: self.bz }); + neighbors.push(LightPosition { x: self.x, y: self.y - 1, z: self.z }); } neighbors } } -impl PartialEq for LightPositionInChunkColumn { +impl PartialEq for LightPosition { fn eq(&self, other: &Self) -> bool { self.y == other.y } } -impl From for BlockPositionInChunkColumn { - fn from(val: LightPositionInChunkColumn) -> Self { +impl From for BlockPositionInChunkColumn { + fn from(val: LightPosition) -> Self { BlockPositionInChunkColumn { - bx: val.bx, + bx: val.x.rem_euclid(16) as u8, y: val.y as i32 - 64 - 16, // TODO: Use the world config - bz: val.bz, + bz: val.x.rem_euclid(16) as u8, } } } -impl From for LightPositionInChunkColumn { - fn from(val: BlockPositionInChunkColumn) -> Self { - LightPositionInChunkColumn { - bx: val.bx, - y: (val.y + 64 + 16) as usize, //-TODO: Use the world config - bz: val.bz, - } - } -} - -impl AddAssign for LightPositionInChunkColumn { +impl AddAssign for LightPosition { fn add_assign(&mut self, rhs: usize) { self.y += rhs; } } -impl std::cmp::Eq for LightPositionInChunkColumn {} +impl std::cmp::Eq for LightPosition {} -impl std::cmp::PartialOrd for LightPositionInChunkColumn { +impl std::cmp::PartialOrd for LightPosition { fn partial_cmp(&self, other: &Self) -> Option { Some(self.y.cmp(&other.y)) } } -impl std::cmp::Ord for LightPositionInChunkColumn { +impl std::cmp::Ord for LightPosition { fn cmp(&self, other: &Self) -> std::cmp::Ordering { self.y.cmp(&other.y) } } impl ChunkColumn { - pub(super) fn init_light(&mut self) -> Result { - self.propagate_sky_light_inside() - } - - fn propagate_sky_light_inside(&mut self) -> Result { + /*fn propagate_sky_light_inside(&mut self) -> Result { let mut to_propagate = EdgesLightToPropagate::new(); // Set all highest blocks to the highest block let highest_blocks = self.get_highest_block(); @@ -412,9 +344,9 @@ impl ChunkColumn { to_propagate.expand(self.explore_sky_light_from_heap(&mut to_explore).map_err(|_| error!("Error while updating light"))?); Ok(to_propagate) - } + }*/ - fn explore_sky_light_from_heap(&mut self, to_explore: &mut BinaryHeap) -> Result { + /*fn explore_sky_light_from_heap(&mut self, to_explore: &mut BinaryHeap) -> Result { // We get the neighbors and determine the light source from them // The neighbor with the highest light level is the light source // So we explore from it @@ -488,18 +420,7 @@ impl ChunkColumn { } to_propagate.expand(self.explore_sky_light_from_heap(&mut to_explore).map_err(|_| error!("Error while updating light"))?); Ok(to_propagate) - } - - pub(super) fn update_from_edge(&mut self, to_propagate: BinaryHeap<(LightPositionInChunkColumn, u8)>) -> Result<(), ()> { - for (position, level) in to_propagate { - let block = Block::from(self.get_block(position.clone().into())); - if block.is_transparent() { - self.light.sky_light.set_level(position.clone(), level.saturating_sub(block.light_absorption()))?; - self.update_light_as_block_changed_at(position.into())?; - } - } - Ok(()) - } + }*/ } #[cfg(test)] diff --git a/minecraft-server/src/world/map.rs b/minecraft-server/src/world/map.rs index 8e7342ec..38b58465 100644 --- a/minecraft-server/src/world/map.rs +++ b/minecraft-server/src/world/map.rs @@ -1,8 +1,9 @@ -use std::{collections::{HashMap, BinaryHeap}, cmp::Ordering, vec}; +use std::{collections::HashMap, cmp::Ordering, vec}; use minecraft_protocol::{components::chunk::PalettedData, ids::blocks::Block}; use tokio::sync::RwLock; -use crate::{prelude::*, world::light::EdgesLightToPropagate}; -use super::light::{Light, LightPositionInChunkColumn}; +use crate::prelude::*; +use super::light::Light; + pub struct WorldMap { /// The map is divided in shards. @@ -10,9 +11,22 @@ pub struct WorldMap { /// The shards are locked independently. /// This allows high concurrency. shard_count: usize, + light_manager: LightManager, shards: Vec>>, } +struct LightManager { + locked_chunks: HashSet, +} + +impl LightManager { + pub fn new() -> Self { + Self { + locked_chunks: HashSet::new(), + } + } +} + #[derive(Clone)] pub(super) struct Chunk { data: NetworkChunk, @@ -329,7 +343,6 @@ impl ChunkColumn { let mut current_height = current_height - 1; // Downward propagation for chunk in self.chunks.iter().rev().skip(n_chunk_to_skip) { - //println!("index: {:?}", (current_height % 16) as u8 + 1); for by in (0..((((current_height) % 16) + 1) as u8)).rev() { let block: BlockWithState = chunk.get_block(BlockPositionInChunk { bx: position.bx, by, bz: position.bz }); // SAFETY: fom_id will get a valid block necessarily @@ -357,7 +370,6 @@ impl ChunkColumn { light: Light::new(), }; column.init_chunk_heightmap(); - let _ = column.init_light().map_err(|_| error!("Failed to init light in chunk column")); column } @@ -442,7 +454,11 @@ impl WorldMap { for _ in 0..shard_count { shards.push(RwLock::new(HashMap::new())); } - WorldMap { shard_count, shards } + WorldMap { + shard_count, + shards, + light_manager: LightManager::new(), + } } pub async fn get_block(&self, position: BlockPosition) -> BlockWithState { @@ -485,7 +501,7 @@ impl WorldMap { } pub async fn set_block(&self, position: BlockPosition, block: BlockWithState) { - async fn inner_set_block(s: &WorldMap, position: BlockPosition, block: BlockWithState) -> Option<(EdgesLightToPropagate, ChunkColumnPosition)> { + async fn inner_set_block(s: &WorldMap, position: BlockPosition, block: BlockWithState) -> Option<()> { let chunk_position = position.chunk(); let position_in_chunk_column = position.in_chunk_column(); let chunk_column_position = chunk_position.chunk_column(); @@ -494,17 +510,10 @@ impl WorldMap { let mut shard = s.shards[shard].write().await; let chunk_column = shard.get_mut(&chunk_column_position)?; chunk_column.set_block(position_in_chunk_column.clone(), block); - chunk_column.update_light_as_block_changed_at(position_in_chunk_column).ok().map(|to_propagate| (to_propagate, chunk_column_position)) + Some(()) } - let to_propagate = inner_set_block(self, position, block).await; - if let Some(to_propagate) = to_propagate { - let (to_propagate, from) = to_propagate; - let to_popagate = to_propagate.chunk_positions_to_propagate(from); - for (chunk_column_position, to_propagate) in to_popagate { - self.update_light_from_edge(chunk_column_position, to_propagate).await; - } - } + inner_set_block(self, position, block).await; } pub async fn get_skylight(&self, position: BlockPosition) -> u8 { @@ -521,7 +530,7 @@ impl WorldMap { inner_get_skylight(self, position).await.unwrap_or(0) } - async fn update_light_from_edge(&self, chunk_column_position: ChunkColumnPosition, to_propagate: BinaryHeap<(LightPositionInChunkColumn, u8)>) { + /*async fn update_light_from_edge(&self, chunk_column_position: ChunkColumnPosition, to_propagate: BinaryHeap<(LightPositionInChunkColumn, u8)>) { async fn inner_get_skylight(s: &WorldMap, chunk_column_position: ChunkColumnPosition, to_propagate: BinaryHeap<(LightPositionInChunkColumn, u8)>) -> Option<()> { let shard = chunk_column_position.shard(s.shard_count); @@ -531,26 +540,7 @@ impl WorldMap { Some(()) } inner_get_skylight(self, chunk_column_position, to_propagate).await; - } - - pub async fn try_move(&self, object: &CollisionShape, movement: &Translation) -> Translation { - // TODO(perf): Optimize Map.try_move by preventing block double-checking - // Also lock the map only once - let movement_fragments = movement.clone().fragment(object); - let mut validated = Translation{ x: 0.0, y: 0.0, z: 0.0 }; - for fragment in movement_fragments { - let validating = validated.clone() + fragment; - let translated_object = object.clone() + &validating; - for block in translated_object.containing_blocks() { - let block = self.get_block(block).await; - if block.block_id() != 0 { - return validated; - } - } - validated = validating; - } - movement.clone() // Would be more logic if it returned validated, but this way we avoid precision errors - } + }*/ pub async fn try_move(&self, object: &CollisionShape, movement: &Translation) -> Translation { // TODO(perf): Optimize Map.try_move by preventing block double-checking @@ -785,45 +775,6 @@ mod tests { } - #[tokio::test] - async fn test_sky_light_flat_chunk() { - let world = WorldMap::new(100); - world.load(ChunkColumnPosition { cx: 0, cz: 0 }).await; - - // Check that the sky light is equal to the light level above the grass and on the top of the world. - for x in 0..16 { - for z in 0..16 { - assert_eq!(world.get_skylight(BlockPosition { x, y: -60, z}).await, 0); - assert_eq!(world.get_skylight(BlockPosition { x, y: -49, z}).await, 0); - assert_eq!(world.get_skylight(BlockPosition { x, y: 120, z}).await, 15); - } - } - - // Break the grass block and check that the sky light is correct. - assert_ne!(world.get_skylight(BlockPosition { x: 0, y: -49, z: 0}).await, 15); - world.set_block(BlockPosition { x: 0, y: -49, z: 0 }, BlockWithState::Air).await; - assert_eq!(world.get_skylight(BlockPosition { x: 0, y: -49, z: 0}).await, 15); - - assert_ne!(world.get_skylight(BlockPosition { x: 0, y: -50, z: 0}).await, 15); - world.set_block(BlockPosition { x: 0, y: -50, z: 0 }, BlockWithState::Air).await; - assert_eq!(world.get_skylight(BlockPosition { x: 0, y: -50, z: 0}).await, 15); - - assert_ne!(world.get_skylight(BlockPosition { x: 0, y: -50, z: 1}).await, 14); - world.set_block(BlockPosition { x: 0, y: -50, z: 1 }, BlockWithState::Air).await; - assert_eq!(world.get_skylight(BlockPosition { x: 0, y: -50, z: 1}).await, 14); - - // test on chunk border - world.load(ChunkColumnPosition { cx: 1, cz: 0 }).await; - world.load(ChunkColumnPosition { cx: 0, cz: 1 }).await; - world.load(ChunkColumnPosition { cx: 1, cz: 1 }).await; - world.load(ChunkColumnPosition { cx: -1, cz: -1 }).await; - - assert_ne!(world.get_skylight(BlockPosition { x: 0, y: -50, z: -1}).await, 14); - world.set_block(BlockPosition { x: 0, y: -50, z: -1 }, BlockWithState::Air).await; - assert_eq!(world.get_skylight(BlockPosition { x: 0, y: -50, z: -1}).await, 14); - } - - #[test] fn benchmark_get_block() { let start_time = std::time::Instant::now(); diff --git a/minecraft-server/src/world/mod.rs b/minecraft-server/src/world/mod.rs index 75430853..8926d3b5 100644 --- a/minecraft-server/src/world/mod.rs +++ b/minecraft-server/src/world/mod.rs @@ -7,7 +7,8 @@ use loading_manager::*; mod map; use map::*; mod light; -use light::*;mod ecs; +use light::*; +mod ecs; use ecs::*; mod collisions; pub use collisions::*; From 8df445e2b89966f15587c0957a4b7f0336df42bd Mon Sep 17 00:00:00 2001 From: DimitriTimoz Date: Sun, 19 Nov 2023 19:43:17 +0100 Subject: [PATCH 36/46] Put architecture --- minecraft-server/src/world/light.rs | 47 +++++++++++++++++++++++++---- minecraft-server/src/world/map.rs | 35 +++++++-------------- minecraft-server/src/world/mod.rs | 4 +-- 3 files changed, 54 insertions(+), 32 deletions(-) diff --git a/minecraft-server/src/world/light.rs b/minecraft-server/src/world/light.rs index c3894e2b..de30728b 100644 --- a/minecraft-server/src/world/light.rs +++ b/minecraft-server/src/world/light.rs @@ -1,4 +1,4 @@ -use std::ops::AddAssign; +use std::{ops::AddAssign, collections::BinaryHeap}; use crate::prelude::*; use super::*; @@ -242,6 +242,13 @@ impl From for LightPositionInChunkColumn { } } +#[derive(Debug, Clone)] +pub struct LightPosition { + pub x: i32, + pub y: usize, + pub z: i32, +} + impl From for LightPositionInChunkColumn { fn from(val: LightPosition) -> Self { LightPositionInChunkColumn { @@ -252,11 +259,14 @@ impl From for LightPositionInChunkColumn { } } -#[derive(Debug, Clone)] -pub struct LightPosition { - pub x: i32, - pub y: usize, - pub z: i32, +impl From for LightPosition { + fn from(val: BlockPosition) -> Self { + Self { + x: val.x, + y: (val.y + 64 + 16) as usize, + z: val.z, + } + } } impl LightPosition { @@ -320,6 +330,31 @@ impl std::cmp::Ord for LightPosition { } } +pub struct LightManager { + world_map: &'static WorldMap, +} + +impl LightManager { + pub fn set_block(world_map: &'static WorldMap, position: BlockPosition, block: BlockWithState) { + let mut to_explore = BinaryHeap::new(); + let position = LightPosition::from(position); + to_explore.extend(position.get_neighbors(24)); + while let Some(postion) = to_explore.pop() { + + } + + // Clear locked chunks + } + + + pub fn init_chunk_column_light(&mut self, chunk_column_position: ChunkColumnPosition) { + unimplemented!(); + + // Clear locked chubks + } +} + + impl ChunkColumn { /*fn propagate_sky_light_inside(&mut self) -> Result { let mut to_propagate = EdgesLightToPropagate::new(); diff --git a/minecraft-server/src/world/map.rs b/minecraft-server/src/world/map.rs index 38b58465..019e52cf 100644 --- a/minecraft-server/src/world/map.rs +++ b/minecraft-server/src/world/map.rs @@ -1,7 +1,7 @@ use std::{collections::HashMap, cmp::Ordering, vec}; use minecraft_protocol::{components::chunk::PalettedData, ids::blocks::Block}; use tokio::sync::RwLock; -use crate::prelude::*; +use crate::{prelude::*, world::light::LightManager}; use super::light::Light; @@ -11,22 +11,9 @@ pub struct WorldMap { /// The shards are locked independently. /// This allows high concurrency. shard_count: usize, - light_manager: LightManager, shards: Vec>>, } -struct LightManager { - locked_chunks: HashSet, -} - -impl LightManager { - pub fn new() -> Self { - Self { - locked_chunks: HashSet::new(), - } - } -} - #[derive(Clone)] pub(super) struct Chunk { data: NetworkChunk, @@ -457,7 +444,6 @@ impl WorldMap { WorldMap { shard_count, shards, - light_manager: LightManager::new(), } } @@ -500,8 +486,8 @@ impl WorldMap { Some(serialized) } - pub async fn set_block(&self, position: BlockPosition, block: BlockWithState) { - async fn inner_set_block(s: &WorldMap, position: BlockPosition, block: BlockWithState) -> Option<()> { + pub async fn set_block(&'static self, position: BlockPosition, block: BlockWithState) { + async fn inner_set_block(s: &'static WorldMap, position: BlockPosition, block: BlockWithState) -> Option<()> { let chunk_position = position.chunk(); let position_in_chunk_column = position.in_chunk_column(); let chunk_column_position = chunk_position.chunk_column(); @@ -513,7 +499,8 @@ impl WorldMap { Some(()) } - inner_set_block(self, position, block).await; + inner_set_block(self, position.clone(), block.clone()).await; + LightManager::set_block(self, position, block); } pub async fn get_skylight(&self, position: BlockPosition) -> u8 { @@ -686,16 +673,16 @@ mod tests { #[tokio::test] async fn test_world_map() { - let map = WorldMap::new(1); + let world_map = Box::leak(Box::new(WorldMap::new(1))); for cx in -3..=3 { for cz in -3..=3 { - map.load(ChunkColumnPosition { cx, cz }).await; + world_map.load(ChunkColumnPosition { cx, cz }).await; } } // Test single block - map.set_block(BlockPosition { x: -40, y: -40, z: -40 }, BlockWithState::RedstoneBlock).await; - let block = map.get_block(BlockPosition { x: -40, y: -40, z: -40 }).await; + world_map.set_block(BlockPosition { x: -40, y: -40, z: -40 }, BlockWithState::RedstoneBlock).await; + let block = world_map.get_block(BlockPosition { x: -40, y: -40, z: -40 }).await; assert_eq!(block.block_state_id().unwrap(), BlockWithState::RedstoneBlock.block_state_id().unwrap()); // Set blocks @@ -703,7 +690,7 @@ mod tests { for x in (-40..40).step_by(9) { for y in (-40..200).step_by(15) { for z in (-40..40).step_by(9) { - map.set_block(BlockPosition { x, y, z }, BlockWithState::from_state_id(id).unwrap()).await; + world_map.set_block(BlockPosition { x, y, z }, BlockWithState::from_state_id(id).unwrap()).await; id += 1; } } @@ -714,7 +701,7 @@ mod tests { for x in (-40..40).step_by(9) { for y in (-40..200).step_by(15) { for z in (-40..40).step_by(9) { - let got = map.get_block(BlockPosition { x, y, z }).await.block_state_id().unwrap(); + let got = world_map.get_block(BlockPosition { x, y, z }).await.block_state_id().unwrap(); assert_eq!(id, got); id += 1; } diff --git a/minecraft-server/src/world/mod.rs b/minecraft-server/src/world/mod.rs index 8926d3b5..92cee17a 100644 --- a/minecraft-server/src/world/mod.rs +++ b/minecraft-server/src/world/mod.rs @@ -43,7 +43,7 @@ impl World { self.map.get_network_chunk_column_data(position).await } - pub async fn set_block(&self, position: BlockPosition, block: BlockWithState) { + pub async fn set_block(&'static self, position: BlockPosition, block: BlockWithState) { self.map.set_block(position.clone(), block.clone()).await; self.notify(&position.chunk_column(), WorldChange::Block(position, block)).await; } @@ -168,7 +168,7 @@ mod tests { #[tokio::test] async fn test_world_notifications() { - let world = World::new(broadcast_channel(100).1); + let world = Box::leak(Box::new(World::new(broadcast_channel(100).1))); let mut receiver1 = world.add_loader(1).await; let mut receiver2 = world.add_loader(2).await; From eb13f2e31775e2790a8adb0a5cfaee197df05775 Mon Sep 17 00:00:00 2001 From: DimitriTimoz Date: Sun, 19 Nov 2023 22:18:31 +0100 Subject: [PATCH 37/46] WIP abstraction --- minecraft-positions/src/shards.rs | 5 ++- minecraft-server/src/world/light.rs | 48 +++++++++++++++++++++++++---- minecraft-server/src/world/map.rs | 10 +++++- 3 files changed, 55 insertions(+), 8 deletions(-) diff --git a/minecraft-positions/src/shards.rs b/minecraft-positions/src/shards.rs index 3d042bb2..4ef0989d 100644 --- a/minecraft-positions/src/shards.rs +++ b/minecraft-positions/src/shards.rs @@ -2,6 +2,9 @@ use crate::*; impl ChunkColumnPosition { pub fn shard(&self, shard_count: usize) -> usize { - (self.cx + self.cz).unsigned_abs() as usize % shard_count + const REGION_SIZE: i32 = 8; + let region_x = self.cx.div_euclid(REGION_SIZE); + let region_z = self.cz.div_euclid(REGION_SIZE); + (region_x + region_z).unsigned_abs() as usize % shard_count } } diff --git a/minecraft-server/src/world/light.rs b/minecraft-server/src/world/light.rs index de30728b..45dbe68d 100644 --- a/minecraft-server/src/world/light.rs +++ b/minecraft-server/src/world/light.rs @@ -1,4 +1,6 @@ use std::{ops::AddAssign, collections::BinaryHeap}; +use minecraft_protocol::ids::blocks::Block; +use tokio::sync::RwLockWriteGuard; use crate::prelude::*; use super::*; @@ -330,20 +332,54 @@ impl std::cmp::Ord for LightPosition { } } -pub struct LightManager { - world_map: &'static WorldMap, -} +pub struct LightManager; impl LightManager { - pub fn set_block(world_map: &'static WorldMap, position: BlockPosition, block: BlockWithState) { + + async fn get_chunk_column<'a>(world_map: &'static WorldMap, block_position: BlockPosition, locked_shards: &'a mut HashMap>>) -> Option<&'a mut ChunkColumn> { + let chunk_column_position = block_position.chunk().chunk_column(); + let shard_id = chunk_column_position.shard(world_map.get_shard_count()); + + // Check that shard is already locked + if let Some(shard) = locked_shards.get_mut(&shard_id) { + // Check that chunk column is already locked + shard.get_mut(&chunk_column_position) + } else { + // Lock the shard + let mut shard = world_map.write_shard(shard_id).await; + + // Lock the chunk column + shard.get_mut(&chunk_column_position) + } + } + + pub async fn set_block(world_map: &'static WorldMap, block_position: BlockPosition, block: BlockWithState) { + let mut locked_shards: HashMap>> = HashMap::new(); + let mut to_explore = BinaryHeap::new(); - let position = LightPosition::from(position); + let position = LightPosition::from(block_position.clone()); to_explore.extend(position.get_neighbors(24)); while let Some(postion) = to_explore.pop() { - + let chunk_column_position = LightPositionInChunkColumn::from(postion); + + let column = Self::get_chunk_column(world_map, block_position.clone(), &mut locked_shards).await; + if let Some(column) = column { + let block = Block::from(column.get_block(position.clone().into())); + + if block.is_transparent() { + let highest_block = column.get_highest_block_at(&block_position.in_chunk_column()); + let is_inside = highest_block > postion.clone().y as u16 + 1; + let new_level = if is_inside { postion.clone().y as u8 - block.light_absorption() - 1 } else { 15 }; + let new_position = LightPositionInChunkColumn::from(postion.clone()); + + to_explore.extend(postion.clone().get_neighbors(24)); + } + } + } // Clear locked chunks + } diff --git a/minecraft-server/src/world/map.rs b/minecraft-server/src/world/map.rs index 019e52cf..4524e8e4 100644 --- a/minecraft-server/src/world/map.rs +++ b/minecraft-server/src/world/map.rs @@ -1,6 +1,6 @@ use std::{collections::HashMap, cmp::Ordering, vec}; use minecraft_protocol::{components::chunk::PalettedData, ids::blocks::Block}; -use tokio::sync::RwLock; +use tokio::sync::{RwLock, RwLockWriteGuard}; use crate::{prelude::*, world::light::LightManager}; use super::light::Light; @@ -461,6 +461,10 @@ impl WorldMap { inner_get_block(self, position).await.unwrap_or(BlockWithState::Air) } + pub(super) async fn write_shard(&self, shard: usize) -> RwLockWriteGuard> { + self.shards[shard].write().await + } + pub async fn get_network_chunk_column_data(&self, position: ChunkColumnPosition) -> Option> { let shard = position.shard(self.shard_count); let shard = self.shards[shard].read().await; @@ -566,6 +570,10 @@ impl WorldMap { //shard.remove(&position); // TODO: write to disk } + + pub fn get_shard_count(&self) -> usize { + self.shard_count + } } #[cfg(test)] From 76ec2922f4579370eed206dbd66a43f102837ce8 Mon Sep 17 00:00:00 2001 From: Dimitri Date: Wed, 22 Nov 2023 16:36:01 +0100 Subject: [PATCH 38/46] Abstraction set block --- minecraft-server/src/world/light.rs | 48 +++++++++++++++++------------ 1 file changed, 28 insertions(+), 20 deletions(-) diff --git a/minecraft-server/src/world/light.rs b/minecraft-server/src/world/light.rs index 45dbe68d..a667de7f 100644 --- a/minecraft-server/src/world/light.rs +++ b/minecraft-server/src/world/light.rs @@ -332,37 +332,45 @@ impl std::cmp::Ord for LightPosition { } } -pub struct LightManager; +pub struct LightManager<'a> { + locked_shards: HashMap>> +} + +impl LightManager<'_> { + pub async fn update_light(world_map: &'static WorldMap, block_position: BlockPosition, block: BlockWithState) { + let mut light_manager = LightManager { + locked_shards: HashMap::new(), + }; + + light_manager.set_block(world_map, block_position, block).await; + } + -impl LightManager { + async fn ensure_shard(&mut self, shard_id: usize, world_map: &'static WorldMap) { + if !self.locked_shards.contains_key(&shard_id) { + let shard = world_map.write_shard(shard_id).await; + self.locked_shards.insert(shard_id, shard); + } + } - async fn get_chunk_column<'a>(world_map: &'static WorldMap, block_position: BlockPosition, locked_shards: &'a mut HashMap>>) -> Option<&'a mut ChunkColumn> { + async fn get_chunk_column(&mut self, world_map: &'static WorldMap, block_position: BlockPosition) -> Option<&mut ChunkColumn> { let chunk_column_position = block_position.chunk().chunk_column(); let shard_id = chunk_column_position.shard(world_map.get_shard_count()); - - // Check that shard is already locked - if let Some(shard) = locked_shards.get_mut(&shard_id) { - // Check that chunk column is already locked - shard.get_mut(&chunk_column_position) - } else { - // Lock the shard - let mut shard = world_map.write_shard(shard_id).await; - - // Lock the chunk column - shard.get_mut(&chunk_column_position) - } + + self.ensure_shard(shard_id, world_map).await; + + let shard = self.locked_shards.get_mut(&shard_id)?; + shard.get_mut(&chunk_column_position) } - pub async fn set_block(world_map: &'static WorldMap, block_position: BlockPosition, block: BlockWithState) { - let mut locked_shards: HashMap>> = HashMap::new(); - + pub async fn set_block(&mut self, world_map: &'static WorldMap, block_position: BlockPosition, block: BlockWithState) { let mut to_explore = BinaryHeap::new(); let position = LightPosition::from(block_position.clone()); to_explore.extend(position.get_neighbors(24)); while let Some(postion) = to_explore.pop() { - let chunk_column_position = LightPositionInChunkColumn::from(postion); + let chunk_column_position = LightPositionInChunkColumn::from(postion.clone()); - let column = Self::get_chunk_column(world_map, block_position.clone(), &mut locked_shards).await; + let column = self.get_chunk_column(world_map, block_position.clone()).await; if let Some(column) = column { let block = Block::from(column.get_block(position.clone().into())); From 8573b33b527f1d75235205ff874609a832193807 Mon Sep 17 00:00:00 2001 From: Dimitri Date: Wed, 22 Nov 2023 16:36:31 +0100 Subject: [PATCH 39/46] Fix compile --- minecraft-server/src/world/map.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/minecraft-server/src/world/map.rs b/minecraft-server/src/world/map.rs index 4524e8e4..6cd943ce 100644 --- a/minecraft-server/src/world/map.rs +++ b/minecraft-server/src/world/map.rs @@ -504,7 +504,7 @@ impl WorldMap { } inner_set_block(self, position.clone(), block.clone()).await; - LightManager::set_block(self, position, block); + LightManager::update_light(self, position, block); } pub async fn get_skylight(&self, position: BlockPosition) -> u8 { From 8b213f5db41ee01208ba598e81aa8fd875d8cc4c Mon Sep 17 00:00:00 2001 From: Dimitri Date: Wed, 22 Nov 2023 16:38:03 +0100 Subject: [PATCH 40/46] Await the light updating --- minecraft-server/src/world/map.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/minecraft-server/src/world/map.rs b/minecraft-server/src/world/map.rs index 6cd943ce..a7596e6e 100644 --- a/minecraft-server/src/world/map.rs +++ b/minecraft-server/src/world/map.rs @@ -504,7 +504,7 @@ impl WorldMap { } inner_set_block(self, position.clone(), block.clone()).await; - LightManager::update_light(self, position, block); + LightManager::update_light(self, position, block).await; } pub async fn get_skylight(&self, position: BlockPosition) -> u8 { From cfc501b54981e15140c54c4ca7bb72ce375626f4 Mon Sep 17 00:00:00 2001 From: Dimitri Date: Wed, 22 Nov 2023 16:39:24 +0100 Subject: [PATCH 41/46] Fix warnings --- minecraft-server/src/world/light.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/minecraft-server/src/world/light.rs b/minecraft-server/src/world/light.rs index a667de7f..10e5f9ef 100644 --- a/minecraft-server/src/world/light.rs +++ b/minecraft-server/src/world/light.rs @@ -1,4 +1,4 @@ -use std::{ops::AddAssign, collections::BinaryHeap}; +use std::{ops::AddAssign, collections::{BinaryHeap, hash_map::Entry}}; use minecraft_protocol::ids::blocks::Block; use tokio::sync::RwLockWriteGuard; @@ -347,9 +347,9 @@ impl LightManager<'_> { async fn ensure_shard(&mut self, shard_id: usize, world_map: &'static WorldMap) { - if !self.locked_shards.contains_key(&shard_id) { + if let Entry::Vacant(e) = self.locked_shards.entry(shard_id) { let shard = world_map.write_shard(shard_id).await; - self.locked_shards.insert(shard_id, shard); + e.insert(shard); } } From 2f604662bbea13fc9b5eab8877f64c1d55a56b3b Mon Sep 17 00:00:00 2001 From: Dimitri Date: Wed, 22 Nov 2023 16:43:18 +0100 Subject: [PATCH 42/46] add abstraction signatures --- minecraft-server/src/world/light.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/minecraft-server/src/world/light.rs b/minecraft-server/src/world/light.rs index 10e5f9ef..d7253ed4 100644 --- a/minecraft-server/src/world/light.rs +++ b/minecraft-server/src/world/light.rs @@ -345,7 +345,6 @@ impl LightManager<'_> { light_manager.set_block(world_map, block_position, block).await; } - async fn ensure_shard(&mut self, shard_id: usize, world_map: &'static WorldMap) { if let Entry::Vacant(e) = self.locked_shards.entry(shard_id) { let shard = world_map.write_shard(shard_id).await; @@ -363,6 +362,14 @@ impl LightManager<'_> { shard.get_mut(&chunk_column_position) } + async fn set_light_level(&mut self, position: LightPosition, level: u8) { + unimplemented!(); + } + + async fn get_light_level(&mut self, position: LightPosition) -> u8 { + unimplemented!(); + } + pub async fn set_block(&mut self, world_map: &'static WorldMap, block_position: BlockPosition, block: BlockWithState) { let mut to_explore = BinaryHeap::new(); let position = LightPosition::from(block_position.clone()); From a9f16a1de61c5a09b1a6fcbab0b28cc2b6d25735 Mon Sep 17 00:00:00 2001 From: Dimitri Date: Wed, 22 Nov 2023 22:14:52 +0100 Subject: [PATCH 43/46] Refactor LightManager to use world_map parameter --- minecraft-server/src/world/light.rs | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/minecraft-server/src/world/light.rs b/minecraft-server/src/world/light.rs index d7253ed4..469e6a1a 100644 --- a/minecraft-server/src/world/light.rs +++ b/minecraft-server/src/world/light.rs @@ -333,30 +333,32 @@ impl std::cmp::Ord for LightPosition { } pub struct LightManager<'a> { - locked_shards: HashMap>> + locked_shards: HashMap>>, + world_map: &'static WorldMap, } impl LightManager<'_> { pub async fn update_light(world_map: &'static WorldMap, block_position: BlockPosition, block: BlockWithState) { let mut light_manager = LightManager { locked_shards: HashMap::new(), + world_map, }; - light_manager.set_block(world_map, block_position, block).await; + light_manager.set_block(block_position, block).await; } - async fn ensure_shard(&mut self, shard_id: usize, world_map: &'static WorldMap) { + async fn ensure_shard(&mut self, shard_id: usize) { if let Entry::Vacant(e) = self.locked_shards.entry(shard_id) { - let shard = world_map.write_shard(shard_id).await; + let shard = self.world_map.write_shard(shard_id).await; e.insert(shard); } } - async fn get_chunk_column(&mut self, world_map: &'static WorldMap, block_position: BlockPosition) -> Option<&mut ChunkColumn> { + async fn get_chunk_column(&mut self, block_position: BlockPosition) -> Option<&mut ChunkColumn> { let chunk_column_position = block_position.chunk().chunk_column(); - let shard_id = chunk_column_position.shard(world_map.get_shard_count()); + let shard_id = chunk_column_position.shard(self.world_map.get_shard_count()); - self.ensure_shard(shard_id, world_map).await; + self.ensure_shard(shard_id).await; let shard = self.locked_shards.get_mut(&shard_id)?; shard.get_mut(&chunk_column_position) @@ -370,14 +372,16 @@ impl LightManager<'_> { unimplemented!(); } - pub async fn set_block(&mut self, world_map: &'static WorldMap, block_position: BlockPosition, block: BlockWithState) { + pub async fn set_block(&mut self, block_position: BlockPosition, block: BlockWithState) { let mut to_explore = BinaryHeap::new(); let position = LightPosition::from(block_position.clone()); to_explore.extend(position.get_neighbors(24)); + let mut current_chunk_column = self.get_chunk_column(block_position.clone()).await; while let Some(postion) = to_explore.pop() { let chunk_column_position = LightPositionInChunkColumn::from(postion.clone()); - let column = self.get_chunk_column(world_map, block_position.clone()).await; + let column: Option = None; // Because Delestre said the commited code must compile + if let Some(column) = column { let block = Block::from(column.get_block(position.clone().into())); From b52d85432f7fe184b8939c47082fa7015c935fde Mon Sep 17 00:00:00 2001 From: DimitriTimoz Date: Thu, 21 Dec 2023 20:08:30 +0100 Subject: [PATCH 44/46] sorry --- minecraft-server/src/world/light.rs | 43 +++++++++++++++++++++-------- 1 file changed, 32 insertions(+), 11 deletions(-) diff --git a/minecraft-server/src/world/light.rs b/minecraft-server/src/world/light.rs index 469e6a1a..5b422a5a 100644 --- a/minecraft-server/src/world/light.rs +++ b/minecraft-server/src/world/light.rs @@ -1,6 +1,6 @@ use std::{ops::AddAssign, collections::{BinaryHeap, hash_map::Entry}}; use minecraft_protocol::ids::blocks::Block; -use tokio::sync::RwLockWriteGuard; +use tokio::sync::{RwLockWriteGuard, OwnedRwLockWriteGuard}; use crate::prelude::*; use super::*; @@ -261,6 +261,15 @@ impl From for LightPositionInChunkColumn { } } +impl From for ChunkColumnPosition { + fn from(val: LightPosition) -> Self { + ChunkColumnPosition { + cx: val.x.div_euclid(16), + cz: val.z.div_euclid(16), + } + } +} + impl From for LightPosition { fn from(val: BlockPosition) -> Self { Self { @@ -333,8 +342,10 @@ impl std::cmp::Ord for LightPosition { } pub struct LightManager<'a> { - locked_shards: HashMap>>, + locked_shards: HashMap>>, world_map: &'static WorldMap, + current_chunk_column: Option<&'a mut ChunkColumn>, + current_chunk_column_position: Option, } impl LightManager<'_> { @@ -342,6 +353,8 @@ impl LightManager<'_> { let mut light_manager = LightManager { locked_shards: HashMap::new(), world_map, + current_chunk_column: None, + current_chunk_column_position: None, }; light_manager.set_block(block_position, block).await; @@ -354,8 +367,7 @@ impl LightManager<'_> { } } - async fn get_chunk_column(&mut self, block_position: BlockPosition) -> Option<&mut ChunkColumn> { - let chunk_column_position = block_position.chunk().chunk_column(); + async fn get_chunk_column(&mut self, chunk_column_position: ChunkColumnPosition) -> Option<&mut ChunkColumn> { let shard_id = chunk_column_position.shard(self.world_map.get_shard_count()); self.ensure_shard(shard_id).await; @@ -364,6 +376,18 @@ impl LightManager<'_> { shard.get_mut(&chunk_column_position) } + async fn update_chunk_column(&mut self, light_position: LightPosition) { + let chunk_column_position = ChunkColumnPosition::from(light_position); + let shard_id = chunk_column_position.shard(self.world_map.get_shard_count()); + if let Some(current_chunk_column_position) = &self.current_chunk_column_position { + if current_chunk_column_position != &chunk_column_position { + // Load the new chunk column + self.ensure_shard(shard_id).await; + self.current_chunk_column = self.get_chunk_column(chunk_column_position).await; + } + } + } + async fn set_light_level(&mut self, position: LightPosition, level: u8) { unimplemented!(); } @@ -376,25 +400,22 @@ impl LightManager<'_> { let mut to_explore = BinaryHeap::new(); let position = LightPosition::from(block_position.clone()); to_explore.extend(position.get_neighbors(24)); - let mut current_chunk_column = self.get_chunk_column(block_position.clone()).await; while let Some(postion) = to_explore.pop() { - let chunk_column_position = LightPositionInChunkColumn::from(postion.clone()); - let column: Option = None; // Because Delestre said the commited code must compile + self.update_chunk_column(position.clone()).await; - if let Some(column) = column { + if let Some(column) = &self.current_chunk_column { let block = Block::from(column.get_block(position.clone().into())); if block.is_transparent() { let highest_block = column.get_highest_block_at(&block_position.in_chunk_column()); let is_inside = highest_block > postion.clone().y as u16 + 1; - let new_level = if is_inside { postion.clone().y as u8 - block.light_absorption() - 1 } else { 15 }; + let new_level = if is_inside { postion.clone().y as u8 - block.light_absorption() - 1 } else { MAX_LIGHT_LEVEL }; let new_position = LightPositionInChunkColumn::from(postion.clone()); to_explore.extend(postion.clone().get_neighbors(24)); } - } - + } } // Clear locked chunks From d86896595cf90cff2842a835c136e49294f67c2d Mon Sep 17 00:00:00 2001 From: DimitriTimoz Date: Sat, 23 Dec 2023 18:00:28 +0100 Subject: [PATCH 45/46] Do compile it --- minecraft-server/src/world/light.rs | 61 +++++++++++++++-------------- minecraft-server/src/world/map.rs | 10 ++--- 2 files changed, 37 insertions(+), 34 deletions(-) diff --git a/minecraft-server/src/world/light.rs b/minecraft-server/src/world/light.rs index 5b422a5a..4d414b46 100644 --- a/minecraft-server/src/world/light.rs +++ b/minecraft-server/src/world/light.rs @@ -245,7 +245,7 @@ impl From for LightPositionInChunkColumn { } #[derive(Debug, Clone)] -pub struct LightPosition { +struct LightPosition { pub x: i32, pub y: usize, pub z: i32, @@ -280,6 +280,16 @@ impl From for LightPosition { } } +impl From for BlockPosition { + fn from(val: LightPosition) -> Self { + Self { + x: val.x, + y: val.y as i32 - 64 - 16, + z: val.z + } + } +} + impl LightPosition { pub fn in_chunk(&self) -> BlockPositionInChunk { BlockPositionInChunk { @@ -341,30 +351,31 @@ impl std::cmp::Ord for LightPosition { } } -pub struct LightManager<'a> { - locked_shards: HashMap>>, +pub struct LightManager { world_map: &'static WorldMap, - current_chunk_column: Option<&'a mut ChunkColumn>, - current_chunk_column_position: Option, + current_shard_id: Option, + current_shard: Option>>, } -impl LightManager<'_> { +impl LightManager { pub async fn update_light(world_map: &'static WorldMap, block_position: BlockPosition, block: BlockWithState) { let mut light_manager = LightManager { - locked_shards: HashMap::new(), world_map, - current_chunk_column: None, - current_chunk_column_position: None, + current_shard: None, + current_shard_id: None, }; light_manager.set_block(block_position, block).await; } async fn ensure_shard(&mut self, shard_id: usize) { - if let Entry::Vacant(e) = self.locked_shards.entry(shard_id) { - let shard = self.world_map.write_shard(shard_id).await; - e.insert(shard); + if let Some(current_shard_id) = self.current_shard_id { + if current_shard_id == shard_id { + return; + } } + self.current_shard = Some(self.world_map.write_shard(shard_id).await); + self.current_shard_id = Some(shard_id); } async fn get_chunk_column(&mut self, chunk_column_position: ChunkColumnPosition) -> Option<&mut ChunkColumn> { @@ -372,22 +383,16 @@ impl LightManager<'_> { self.ensure_shard(shard_id).await; - let shard = self.locked_shards.get_mut(&shard_id)?; - shard.get_mut(&chunk_column_position) - } - - async fn update_chunk_column(&mut self, light_position: LightPosition) { - let chunk_column_position = ChunkColumnPosition::from(light_position); - let shard_id = chunk_column_position.shard(self.world_map.get_shard_count()); - if let Some(current_chunk_column_position) = &self.current_chunk_column_position { - if current_chunk_column_position != &chunk_column_position { - // Load the new chunk column - self.ensure_shard(shard_id).await; - self.current_chunk_column = self.get_chunk_column(chunk_column_position).await; - } - } + if let Some(shard) = &mut self.current_shard { + // Here, we use a reference to `shard` instead of trying to move it + shard.get_mut(&chunk_column_position) + } else { + unreachable!("ensure shard always sets to current_shard the requested shard") + } } + + async fn set_light_level(&mut self, position: LightPosition, level: u8) { unimplemented!(); } @@ -401,10 +406,8 @@ impl LightManager<'_> { let position = LightPosition::from(block_position.clone()); to_explore.extend(position.get_neighbors(24)); while let Some(postion) = to_explore.pop() { - - self.update_chunk_column(position.clone()).await; - if let Some(column) = &self.current_chunk_column { + if let Some(column) = self.get_chunk_column(position.clone().into()).await { let block = Block::from(column.get_block(position.clone().into())); if block.is_transparent() { diff --git a/minecraft-server/src/world/map.rs b/minecraft-server/src/world/map.rs index a7596e6e..e1120857 100644 --- a/minecraft-server/src/world/map.rs +++ b/minecraft-server/src/world/map.rs @@ -1,6 +1,6 @@ use std::{collections::HashMap, cmp::Ordering, vec}; use minecraft_protocol::{components::chunk::PalettedData, ids::blocks::Block}; -use tokio::sync::{RwLock, RwLockWriteGuard}; +use tokio::sync::{RwLock, OwnedRwLockWriteGuard}; use crate::{prelude::*, world::light::LightManager}; use super::light::Light; @@ -11,7 +11,7 @@ pub struct WorldMap { /// The shards are locked independently. /// This allows high concurrency. shard_count: usize, - shards: Vec>>, + shards: Vec>>>, } #[derive(Clone)] @@ -439,7 +439,7 @@ impl WorldMap { pub fn new(shard_count: usize) -> WorldMap { let mut shards = Vec::new(); for _ in 0..shard_count { - shards.push(RwLock::new(HashMap::new())); + shards.push(Arc::new(RwLock::new(HashMap::new()))); } WorldMap { shard_count, @@ -461,8 +461,8 @@ impl WorldMap { inner_get_block(self, position).await.unwrap_or(BlockWithState::Air) } - pub(super) async fn write_shard(&self, shard: usize) -> RwLockWriteGuard> { - self.shards[shard].write().await + pub(super) async fn write_shard(&self, shard: usize) -> OwnedRwLockWriteGuard> { + self.shards[shard].clone().write_owned().await } pub async fn get_network_chunk_column_data(&self, position: ChunkColumnPosition) -> Option> { From 7f656b29e106845c05239b3ca8ac0f7ef288488e Mon Sep 17 00:00:00 2001 From: DimitriTimoz Date: Sat, 23 Dec 2023 19:03:54 +0100 Subject: [PATCH 46/46] Init independant light --- minecraft-server/src/world/light.rs | 36 +++++++++++++++++++++++++---- minecraft-server/src/world/map.rs | 33 ++++++++++++++++---------- minecraft-server/src/world/mod.rs | 2 +- 3 files changed, 53 insertions(+), 18 deletions(-) diff --git a/minecraft-server/src/world/light.rs b/minecraft-server/src/world/light.rs index 4d414b46..54d685ad 100644 --- a/minecraft-server/src/world/light.rs +++ b/minecraft-server/src/world/light.rs @@ -358,12 +358,16 @@ pub struct LightManager { } impl LightManager { - pub async fn update_light(world_map: &'static WorldMap, block_position: BlockPosition, block: BlockWithState) { - let mut light_manager = LightManager { + fn new(world_map: &'static WorldMap) -> Self { + Self { world_map, current_shard: None, current_shard_id: None, - }; + } + } + + pub async fn update_light(world_map: &'static WorldMap, block_position: BlockPosition, block: BlockWithState) { + let mut light_manager = Self::new(world_map); light_manager.set_block(block_position, block).await; } @@ -426,8 +430,7 @@ impl LightManager { } - pub fn init_chunk_column_light(&mut self, chunk_column_position: ChunkColumnPosition) { - unimplemented!(); + pub async fn init_chunk_column_light(world_map: &'static WorldMap, chunk_column_position: ChunkColumnPosition) { // Clear locked chubks } @@ -435,6 +438,29 @@ impl LightManager { impl ChunkColumn { + /// Init independant light means it will compute the light for all the chunk without considering the neighbour chunks. + + pub(super) fn init_independant_light(&mut self) { + let _ = self.light.sky_light.set_region(self.get_highest_block() as usize + 1, ChunkColumn::MAX_HEIGHT as usize, self.light.sky_light.level); + + for x in 0..16 { + for z in 0..16 { + for y in self.get_highest_block_at(&BlockPositionInChunkColumn { + bx: x, + y: 0i32, + bz: z + })..(self.get_highest_block() as u16) { + let _ = self.light.sky_light.set_level( + LightPositionInChunkColumn { + bx: x, + y: y as usize, + bz: z + }, self.light.sky_light.level); + } + } + } + } + /*fn propagate_sky_light_inside(&mut self) -> Result { let mut to_propagate = EdgesLightToPropagate::new(); // Set all highest blocks to the highest block diff --git a/minecraft-server/src/world/map.rs b/minecraft-server/src/world/map.rs index e1120857..777c781f 100644 --- a/minecraft-server/src/world/map.rs +++ b/minecraft-server/src/world/map.rs @@ -307,8 +307,8 @@ pub(super) struct ChunkColumn { } impl ChunkColumn { - const MAX_HEIGHT: u16 = 320 + 64; // TODO: adapt to the world height - const MIN_Y: i32 = -64; + pub const MAX_HEIGHT: u16 = 320 + 64; // TODO: adapt to the world height + pub const MIN_Y: i32 = -64; fn init_chunk_heightmap(&mut self){ self.heightmap = HeightMap::new(9); @@ -357,6 +357,7 @@ impl ChunkColumn { light: Light::new(), }; column.init_chunk_heightmap(); + column.init_independant_light(); column } @@ -552,13 +553,14 @@ impl WorldMap { movement.clone() // Would be more logic if it returned validated, but this way we avoid precision errors } - pub async fn load(&self, position: ChunkColumnPosition) { + pub async fn load(&'static self, position: ChunkColumnPosition) { let chunk = ChunkColumn::flat(); // TODO: load from disk let shard = position.shard(self.shard_count); trace!("Loading chunk column at {:?}", position); let mut shard = self.shards[shard].write().await; - shard.entry(position).or_insert_with(|| chunk); + shard.entry(position.clone()).or_insert_with(|| chunk); + LightManager::init_chunk_column_light(self, position).await; } pub async fn unload(&self, _position: ChunkColumnPosition) { @@ -767,7 +769,6 @@ mod tests { // Check that the heightmap is correct after setting the highest block to air flat_column.set_block(BlockPositionInChunkColumn { bx: 0, y: 10, bz: 0 }, BlockWithState::Air); assert_eq!(flat_column.heightmap.get(&BlockPositionInChunkColumn { bx: 0, y: 0, bz: 0 }), 67); - } #[test] @@ -782,12 +783,10 @@ mod tests { println!("Elapsed: {:?}", elapsed / 441); } - - #[tokio::test] async fn test_try_move() { - let map = WorldMap::new(1); - map.load(ChunkColumnPosition { cx: 0, cz: 0 }).await; + let world_map: &mut WorldMap = Box::leak(Box::new(WorldMap::new(1))); + world_map.load(ChunkColumnPosition { cx: 0, cz: 0 }).await; let bounding_box = CollisionShape { x1: 0.0, y1: 0.0, @@ -800,19 +799,29 @@ mod tests { // Position on ground and try to go through it let positionned_box = bounding_box.clone() + &Translation { x: 0.0, y: -3.0*16.0, z: 0.0 }; let movement = Translation { x: 0.0, y: -10.0, z: 0.0 }; - let movement = map.try_move(&positionned_box, &movement).await; + let movement = world_map.try_move(&positionned_box, &movement).await; assert_eq!(movement, Translation { x: 0.0, y: 0.0, z: 0.0 }); // It doesn't get through // Place it a little above ground let positionned_box = bounding_box.clone() + &Translation { x: 0.0, y: -3.0*16.0 + 1.0, z: 0.0 }; let movement = Translation { x: 0.0, y: -10.0, z: 0.0 }; - let movement = map.try_move(&positionned_box, &movement).await; + let movement = world_map.try_move(&positionned_box, &movement).await; assert_eq!(movement, Translation { x: 0.0, y: -1.0, z: 0.0 }); // It falls down but doesn't get through // Place it above but not on round coordinates let positionned_box = bounding_box.clone() + &Translation { x: 0.0, y: -3.0*16.0 + 1.1, z: 0.2 }; let movement = Translation { x: 2.0, y: -10.0, z: 0.0 }; - let movement = map.try_move(&positionned_box, &movement).await; + let movement = world_map.try_move(&positionned_box, &movement).await; assert_eq!(movement, Translation { x: 0.2200000000000003, y: -1.1000000000000014, z: 0.0 }); // It falls down but doesn't get through } + + #[tokio::test] + async fn test_skylight() { + let world_map = Box::leak(Box::new(WorldMap::new(1))); + world_map.load(ChunkColumnPosition { cx: 0, cz: 0 }).await; + + // Test skylight initialisation for flat map + assert_eq!(world_map.get_skylight(BlockPosition { x: 8, y: 200, z: 8 }).await, 15, "The skylight is not valid for the blocks higher than the highest block"); + + } } diff --git a/minecraft-server/src/world/mod.rs b/minecraft-server/src/world/mod.rs index 92cee17a..fcbf8ad5 100644 --- a/minecraft-server/src/world/mod.rs +++ b/minecraft-server/src/world/mod.rs @@ -62,7 +62,7 @@ impl World { self.change_senders.write().await.remove(&uuid); } - pub async fn update_loaded_chunks(&self, uuid: UUID, loaded_chunks: HashSet) { + pub async fn update_loaded_chunks(&'static self, uuid: UUID, loaded_chunks: HashSet) { let mut loading_manager = self.loading_manager.write().await; let loaded_chunks_before = loading_manager.get_loaded_chunks(); loading_manager.update_loaded_chunks(uuid, loaded_chunks);