From dc2206c5fbcaaabde208befacff52907ad847063 Mon Sep 17 00:00:00 2001 From: ReCore Date: Mon, 16 Dec 2024 18:29:14 +1030 Subject: [PATCH 01/21] Block reading somewhat working --- src/bin/src/packet_handlers/login_process.rs | 4 + .../src/data_packing/errors.rs | 9 ++ .../general_purpose/src/data_packing/i16.rs | 101 ++++++++++++++++++ .../general_purpose/src/data_packing/i32.rs | 101 ++++++++++++++++++ .../general_purpose/src/data_packing/i8.rs | 101 ++++++++++++++++++ .../general_purpose/src/data_packing/mod.rs | 7 ++ .../general_purpose/src/data_packing/u16.rs | 93 ++++++++++++++++ .../general_purpose/src/data_packing/u32.rs | 93 ++++++++++++++++ .../general_purpose/src/data_packing/u8.rs | 92 ++++++++++++++++ src/lib/utils/general_purpose/src/lib.rs | 1 + src/lib/world/src/chunk_format.rs | 10 +- src/lib/world/src/edits.rs | 43 ++++++++ src/lib/world/src/errors.rs | 15 ++- src/lib/world/src/lib.rs | 1 + src/lib/world/src/vanilla_chunk_format.rs | 15 ++- 15 files changed, 676 insertions(+), 10 deletions(-) create mode 100644 src/lib/utils/general_purpose/src/data_packing/errors.rs create mode 100644 src/lib/utils/general_purpose/src/data_packing/i16.rs create mode 100644 src/lib/utils/general_purpose/src/data_packing/i32.rs create mode 100644 src/lib/utils/general_purpose/src/data_packing/i8.rs create mode 100644 src/lib/utils/general_purpose/src/data_packing/mod.rs create mode 100644 src/lib/utils/general_purpose/src/data_packing/u16.rs create mode 100644 src/lib/utils/general_purpose/src/data_packing/u32.rs create mode 100644 src/lib/utils/general_purpose/src/data_packing/u8.rs create mode 100644 src/lib/world/src/edits.rs diff --git a/src/bin/src/packet_handlers/login_process.rs b/src/bin/src/packet_handlers/login_process.rs index 0fc450ea..3806fcc5 100644 --- a/src/bin/src/packet_handlers/login_process.rs +++ b/src/bin/src/packet_handlers/login_process.rs @@ -178,6 +178,10 @@ async fn handle_ack_finish_configuration( chunk_recv.last_chunk = Some((pos.x as i32, pos.z as i32, String::from("overworld"))); chunk_recv.calculate_chunks().await; + debug!( + "Block: {}", + state.world.get_block(1, 163, 2, "overworld").await.unwrap() + ); send_keep_alive(conn_id, state, &mut writer).await?; Ok(ack_finish_configuration_event) diff --git a/src/lib/utils/general_purpose/src/data_packing/errors.rs b/src/lib/utils/general_purpose/src/data_packing/errors.rs new file mode 100644 index 00000000..0ea7f02a --- /dev/null +++ b/src/lib/utils/general_purpose/src/data_packing/errors.rs @@ -0,0 +1,9 @@ +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum DataPackingError { + #[error("Size ({0}) exceeds maximum size of data type: {1}")] + SizeExceedsMaxSize(u8, u8), + #[error("Not enough bits to read with size {0} at offset {1}")] + NotEnoughBits(u8, u32), +} diff --git a/src/lib/utils/general_purpose/src/data_packing/i16.rs b/src/lib/utils/general_purpose/src/data_packing/i16.rs new file mode 100644 index 00000000..d0131f7d --- /dev/null +++ b/src/lib/utils/general_purpose/src/data_packing/i16.rs @@ -0,0 +1,101 @@ +use crate::data_packing::errors::DataPackingError; + +/// Reads a specified number of bits from a given offset in a 64-bit signed integer. +/// +/// # Arguments +/// +/// * `data` - A reference to the 64-bit signed integer to read from. +/// * `size` - The number of bits to read (must be 16 or less). +/// * `offset` - The bit offset from which to start reading. +/// +/// # Returns +/// +/// * `Ok(i16)` - The extracted bits as a 16-bit signed integer. +/// * `Err(DataPackingError)` - If the size exceeds 16 bits or the offset plus size exceeds 64 bits. +/// +/// # Errors +/// +/// * `DataPackingError::SizeExceedsMaxSize` - If `size` is greater than 16. +/// * `DataPackingError::NotEnoughBits` - If `offset + size` exceeds 64 bits. +pub fn read_nbit_i16(data: &i64, size: u8, offset: u32) -> Result { + if size > 16 { + return Err(DataPackingError::SizeExceedsMaxSize(size, 16)); + } + if offset + size as u32 > 64 { + return Err(DataPackingError::NotEnoughBits(size, offset)); + } + let mask = (1 << size) - 1; + let extracted_bits = ((data >> offset) & mask) as i16; + // Sign extend if the extracted bits represent a negative number + let sign_bit = 1 << (size - 1); + if extracted_bits & sign_bit != 0 { + Ok(extracted_bits | !mask as i16) + } else { + Ok(extracted_bits) + } +} + +/// Writes a specified number of bits to a given offset in a 64-bit signed integer. +/// +/// # Arguments +/// +/// * `data` - A mutable reference to the 64-bit signed integer to write to. +/// * `offset` - The bit offset from which to start writing. +/// * `value` - The 16-bit signed integer value to write. +/// * `size` - The number of bits to write (must be 16 or less). +/// +/// # Returns +/// +/// * `Ok(())` - If the bits were successfully written. +/// * `Err(DataPackingError)` - If the size exceeds 16 bits or the offset plus size exceeds 64 bits. +/// +/// # Errors +/// +/// * `DataPackingError::SizeExceedsMaxSize` - If `size` is greater than 16. +/// * `DataPackingError::NotEnoughBits` - If `offset + size` exceeds 64 bits. +pub fn write_nbit_i16( + data: &mut i64, + offset: u32, + value: i16, + size: u8, +) -> Result<(), DataPackingError> { + if size > 16 { + return Err(DataPackingError::SizeExceedsMaxSize(size, 16)); + } + if offset + size as u32 > 64 { + return Err(DataPackingError::NotEnoughBits(size, offset)); + } + let mask = (1 << size) - 1; + *data &= !((mask as i64) << offset); + *data |= ((value as i64) & mask) << offset; + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + /// Tests the `read_nbit_i16` function with various inputs. + #[test] + fn test_read_nbit_i16() { + let data: i64 = 0b110101011; + assert_eq!(read_nbit_i16(&data, 3, 0).unwrap(), 0b011); + assert_eq!(read_nbit_i16(&data, 3, 3).unwrap(), -3); // 0b101 as i16 is -3 + assert_eq!(read_nbit_i16(&data, 3, 6).unwrap(), -2); // 0b110 as i16 is -2 + assert_eq!(read_nbit_i16(&data, 3, 9).unwrap(), 0b000); + } + + /// Tests the `write_nbit_i16` function with various inputs. + #[test] + fn test_write_nbit_i16() { + let mut data: i64 = 0; + write_nbit_i16(&mut data, 0, 0b011, 3).unwrap(); + assert_eq!(data, 0b011); + write_nbit_i16(&mut data, 3, -3, 3).unwrap(); // 0b101 as i16 is -3 + assert_eq!(data, 0b101011); + write_nbit_i16(&mut data, 6, -2, 3).unwrap(); // 0b110 as i16 is -2 + assert_eq!(data, 0b110101011); + write_nbit_i16(&mut data, 9, 0b000, 3).unwrap(); + assert_eq!(data, 0b110101011); + } +} \ No newline at end of file diff --git a/src/lib/utils/general_purpose/src/data_packing/i32.rs b/src/lib/utils/general_purpose/src/data_packing/i32.rs new file mode 100644 index 00000000..18d26a93 --- /dev/null +++ b/src/lib/utils/general_purpose/src/data_packing/i32.rs @@ -0,0 +1,101 @@ +use crate::data_packing::errors::DataPackingError; + +/// Reads a specified number of bits from a given offset in a 64-bit signed integer. +/// +/// # Arguments +/// +/// * `data` - A reference to the 64-bit signed integer to read from. +/// * `size` - The number of bits to read (must be 32 or less). +/// * `offset` - The bit offset from which to start reading. +/// +/// # Returns +/// +/// * `Ok(i32)` - The extracted bits as a 32-bit signed integer. +/// * `Err(DataPackingError)` - If the size exceeds 32 bits or the offset plus size exceeds 64 bits. +/// +/// # Errors +/// +/// * `DataPackingError::SizeExceedsMaxSize` - If `size` is greater than 32. +/// * `DataPackingError::NotEnoughBits` - If `offset + size` exceeds 64 bits. +pub fn read_nbit_i32(data: &i64, size: u8, offset: u32) -> Result { + if size > 32 { + return Err(DataPackingError::SizeExceedsMaxSize(size, 32)); + } + if offset + size as u32 > 64 { + return Err(DataPackingError::NotEnoughBits(size, offset)); + } + let mask = (1 << size) - 1; + let extracted_bits = ((data >> offset) & mask) as i32; + // Sign extend if the extracted bits represent a negative number + let sign_bit = 1 << (size - 1); + if extracted_bits & sign_bit != 0 { + Ok(extracted_bits | !mask as i32) + } else { + Ok(extracted_bits) + } +} + +/// Writes a specified number of bits to a given offset in a 64-bit signed integer. +/// +/// # Arguments +/// +/// * `data` - A mutable reference to the 64-bit signed integer to write to. +/// * `offset` - The bit offset from which to start writing. +/// * `value` - The 32-bit signed integer value to write. +/// * `size` - The number of bits to write (must be 32 or less). +/// +/// # Returns +/// +/// * `Ok(())` - If the bits were successfully written. +/// * `Err(DataPackingError)` - If the size exceeds 32 bits or the offset plus size exceeds 64 bits. +/// +/// # Errors +/// +/// * `DataPackingError::SizeExceedsMaxSize` - If `size` is greater than 32. +/// * `DataPackingError::NotEnoughBits` - If `offset + size` exceeds 64 bits. +pub fn write_nbit_i32( + data: &mut i64, + offset: u32, + value: i32, + size: u8, +) -> Result<(), DataPackingError> { + if size > 32 { + return Err(DataPackingError::SizeExceedsMaxSize(size, 32)); + } + if offset + size as u32 > 64 { + return Err(DataPackingError::NotEnoughBits(size, offset)); + } + let mask = (1 << size) - 1; + *data &= !(mask << offset); + *data |= ((value as i64) & mask) << offset; + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + /// Tests the `read_nbit_i32` function with various inputs. + #[test] + fn test_read_nbit_i32() { + let data: i64 = 0b110101011; + assert_eq!(read_nbit_i32(&data, 3, 0).unwrap(), 0b011); + assert_eq!(read_nbit_i32(&data, 3, 3).unwrap(), -3); // 0b101 as i32 is -3 + assert_eq!(read_nbit_i32(&data, 3, 6).unwrap(), -2); // 0b110 as i32 is -2 + assert_eq!(read_nbit_i32(&data, 3, 9).unwrap(), 0b000); + } + + /// Tests the `write_nbit_i32` function with various inputs. + #[test] + fn test_write_nbit_i32() { + let mut data: i64 = 0; + write_nbit_i32(&mut data, 0, 0b011, 3).unwrap(); + assert_eq!(data, 0b011); + write_nbit_i32(&mut data, 3, -3, 3).unwrap(); // 0b101 as i32 is -3 + assert_eq!(data, 0b101011); + write_nbit_i32(&mut data, 6, -2, 3).unwrap(); // 0b110 as i32 is -2 + assert_eq!(data, 0b110101011); + write_nbit_i32(&mut data, 9, 0b000, 3).unwrap(); + assert_eq!(data, 0b110101011); + } +} diff --git a/src/lib/utils/general_purpose/src/data_packing/i8.rs b/src/lib/utils/general_purpose/src/data_packing/i8.rs new file mode 100644 index 00000000..f95679a5 --- /dev/null +++ b/src/lib/utils/general_purpose/src/data_packing/i8.rs @@ -0,0 +1,101 @@ +use crate::data_packing::errors::DataPackingError; + +/// Reads a specified number of bits from a given offset in a 64-bit signed integer. +/// +/// # Arguments +/// +/// * `data` - A reference to the 64-bit signed integer to read from. +/// * `size` - The number of bits to read (must be 8 or less). +/// * `offset` - The bit offset from which to start reading. +/// +/// # Returns +/// +/// * `Ok(i8)` - The extracted bits as an 8-bit signed integer. +/// * `Err(DataPackingError)` - If the size exceeds 8 bits or the offset plus size exceeds 64 bits. +/// +/// # Errors +/// +/// * `DataPackingError::SizeExceedsMaxSize` - If `size` is greater than 8. +/// * `DataPackingError::NotEnoughBits` - If `offset + size` exceeds 64 bits. +pub fn read_nbit_i8(data: &i64, size: u8, offset: u32) -> Result { + if size > 8 { + return Err(DataPackingError::SizeExceedsMaxSize(size, 8)); + } + if offset + size as u32 > 64 { + return Err(DataPackingError::NotEnoughBits(size, offset)); + } + let mask = (1 << size) - 1; + let extracted_bits = ((data >> offset) & mask) as i8; + // Sign extend if the extracted bits represent a negative number + let sign_bit = 1 << (size - 1); + if extracted_bits & sign_bit != 0 { + Ok(extracted_bits | !mask as i8) + } else { + Ok(extracted_bits) + } +} + +/// Writes a specified number of bits to a given offset in a 64-bit signed integer. +/// +/// # Arguments +/// +/// * `data` - A mutable reference to the 64-bit signed integer to write to. +/// * `offset` - The bit offset from which to start writing. +/// * `value` - The 8-bit signed integer value to write. +/// * `size` - The number of bits to write (must be 8 or less). +/// +/// # Returns +/// +/// * `Ok(())` - If the bits were successfully written. +/// * `Err(DataPackingError)` - If the size exceeds 8 bits or the offset plus size exceeds 64 bits. +/// +/// # Errors +/// +/// * `DataPackingError::SizeExceedsMaxSize` - If `size` is greater than 8. +/// * `DataPackingError::NotEnoughBits` - If `offset + size` exceeds 64 bits. +pub fn write_nbit_i8( + data: &mut i64, + offset: u32, + value: i8, + size: u8, +) -> Result<(), DataPackingError> { + if size > 8 { + return Err(DataPackingError::SizeExceedsMaxSize(size, 8)); + } + if offset + size as u32 > 64 { + return Err(DataPackingError::NotEnoughBits(size, offset)); + } + let mask = (1 << size) - 1; + *data &= !((mask as i64) << offset); + *data |= ((value as i64) & mask) << offset; + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + /// Tests the `write_nbit_i8` function with various inputs. + #[test] + fn test_write_nbit_i8() { + let mut data: i64 = 0; + write_nbit_i8(&mut data, 0, 0b011, 3).unwrap(); + assert_eq!(data, 0b011); + write_nbit_i8(&mut data, 3, -3, 3).unwrap(); // 0b101 as i8 is -3 + assert_eq!(data, 0b101011); + write_nbit_i8(&mut data, 6, -2, 3).unwrap(); // 0b110 as i8 is -2 + assert_eq!(data, 0b110101011); + write_nbit_i8(&mut data, 9, 0b000, 3).unwrap(); + assert_eq!(data, 0b110101011); + } + + /// Tests the `read_nbit_i8` function with various inputs. + #[test] + fn test_read_nbit_i8() { + let data: i64 = 0b110101011; + assert_eq!(read_nbit_i8(&data, 3, 0).unwrap(), 0b011); + assert_eq!(read_nbit_i8(&data, 3, 3).unwrap(), -3); // 0b101 as i8 is -3 + assert_eq!(read_nbit_i8(&data, 3, 6).unwrap(), -2); // 0b110 as i8 is -2 + assert_eq!(read_nbit_i8(&data, 3, 9).unwrap(), 0b000); + } +} diff --git a/src/lib/utils/general_purpose/src/data_packing/mod.rs b/src/lib/utils/general_purpose/src/data_packing/mod.rs new file mode 100644 index 00000000..4afa9f63 --- /dev/null +++ b/src/lib/utils/general_purpose/src/data_packing/mod.rs @@ -0,0 +1,7 @@ +pub mod errors; +pub mod i16; +pub mod i32; +pub mod i8; +pub mod u16; +pub mod u32; +pub mod u8; diff --git a/src/lib/utils/general_purpose/src/data_packing/u16.rs b/src/lib/utils/general_purpose/src/data_packing/u16.rs new file mode 100644 index 00000000..9bc8c06d --- /dev/null +++ b/src/lib/utils/general_purpose/src/data_packing/u16.rs @@ -0,0 +1,93 @@ +use crate::data_packing::errors::DataPackingError; + +/// Reads a specified number of bits from a given offset in a 64-bit unsigned integer. +/// +/// # Arguments +/// +/// * `data` - A reference to the 64-bit unsigned integer to read from. +/// * `size` - The number of bits to read (must be 16 or less). +/// * `offset` - The bit offset from which to start reading. +/// +/// # Returns +/// +/// * `Ok(u16)` - The extracted bits as a 16-bit unsigned integer. +/// * `Err(DataPackingError)` - If the size exceeds 16 bits or the offset plus size exceeds 64 bits. +/// +/// # Errors +/// +/// * `DataPackingError::SizeExceedsMaxSize` - If `size` is greater than 16. +/// * `DataPackingError::NotEnoughBits` - If `offset + size` exceeds 64 bits. +pub fn read_nbit_u16(data: &u64, size: u8, offset: u32) -> Result { + if size > 16 { + return Err(DataPackingError::SizeExceedsMaxSize(size, 16)); + } + if offset + size as u32 > 64 { + return Err(DataPackingError::NotEnoughBits(size, offset)); + } + Ok(((data >> offset) & ((1 << size) - 1)) as u16) +} + +/// Writes a specified number of bits to a given offset in a 64-bit unsigned integer. +/// +/// # Arguments +/// +/// * `data` - A mutable reference to the 64-bit unsigned integer to write to. +/// * `offset` - The bit offset from which to start writing. +/// * `value` - The 16-bit unsigned integer value to write. +/// * `size` - The number of bits to write (must be 16 or less). +/// +/// # Returns +/// +/// * `Ok(())` - If the bits were successfully written. +/// * `Err(DataPackingError)` - If the size exceeds 16 bits or the offset plus size exceeds 64 bits. +/// +/// # Errors +/// +/// * `DataPackingError::SizeExceedsMaxSize` - If `size` is greater than 16. +/// * `DataPackingError::NotEnoughBits` - If `offset + size` exceeds 64 bits. +pub fn write_nbit_u16( + data: &mut u64, + offset: u32, + value: u16, + size: u8, +) -> Result<(), DataPackingError> { + if size > 16 { + return Err(DataPackingError::SizeExceedsMaxSize(size, 16)); + } + if offset + size as u32 > 64 { + return Err(DataPackingError::NotEnoughBits(size, offset)); + } + let mask = (1 << size) - 1; + *data &= !((mask as u64) << offset); + *data |= ((value as u64) & mask) << offset; + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + /// Tests the `read_nbit_u16` function with various inputs. + #[test] + fn test_read_nbit_u16() { + let data: u64 = 0b110101011; + assert_eq!(read_nbit_u16(&data, 3, 0).unwrap(), 0b011); + assert_eq!(read_nbit_u16(&data, 3, 3).unwrap(), 0b101); + assert_eq!(read_nbit_u16(&data, 3, 6).unwrap(), 0b110); + assert_eq!(read_nbit_u16(&data, 3, 9).unwrap(), 0b000); + } + + /// Tests the `write_nbit_u16` function with various inputs. + #[test] + fn test_write_nbit_u16() { + let mut data: u64 = 0; + write_nbit_u16(&mut data, 0, 0b011, 3).unwrap(); + assert_eq!(data, 0b011); + write_nbit_u16(&mut data, 3, 0b101, 3).unwrap(); + assert_eq!(data, 0b101011); + write_nbit_u16(&mut data, 6, 0b110, 3).unwrap(); + assert_eq!(data, 0b110101011); + write_nbit_u16(&mut data, 9, 0b000, 3).unwrap(); + assert_eq!(data, 0b110101011); + } +} diff --git a/src/lib/utils/general_purpose/src/data_packing/u32.rs b/src/lib/utils/general_purpose/src/data_packing/u32.rs new file mode 100644 index 00000000..6e498ebf --- /dev/null +++ b/src/lib/utils/general_purpose/src/data_packing/u32.rs @@ -0,0 +1,93 @@ +use crate::data_packing::errors::DataPackingError; + +/// Reads a specified number of bits from a given offset in a 64-bit unsigned integer. +/// +/// # Arguments +/// +/// * `data` - A reference to the 64-bit unsigned integer to read from. +/// * `size` - The number of bits to read (must be 32 or less). +/// * `offset` - The bit offset from which to start reading. +/// +/// # Returns +/// +/// * `Ok(u32)` - The extracted bits as a 32-bit unsigned integer. +/// * `Err(DataPackingError)` - If the size exceeds 32 bits or the offset plus size exceeds 64 bits. +/// +/// # Errors +/// +/// * `DataPackingError::SizeExceedsMaxSize` - If `size` is greater than 32. +/// * `DataPackingError::NotEnoughBits` - If `offset + size` exceeds 64 bits. +pub fn read_nbit_u32(data: &i64, size: u8, offset: u32) -> Result { + if size > 32 { + return Err(DataPackingError::SizeExceedsMaxSize(size, 32)); + } + if offset + size as u32 > 64 { + return Err(DataPackingError::NotEnoughBits(size, offset)); + } + Ok(((data >> offset) & ((1 << size) - 1)) as u32) +} + +/// Writes a specified number of bits to a given offset in a 64-bit unsigned integer. +/// +/// # Arguments +/// +/// * `data` - A mutable reference to the 64-bit unsigned integer to write to. +/// * `offset` - The bit offset from which to start writing. +/// * `value` - The 32-bit unsigned integer value to write. +/// * `size` - The number of bits to write (must be 32 or less). +/// +/// # Returns +/// +/// * `Ok(())` - If the bits were successfully written. +/// * `Err(DataPackingError)` - If the size exceeds 32 bits or the offset plus size exceeds 64 bits. +/// +/// # Errors +/// +/// * `DataPackingError::SizeExceedsMaxSize` - If `size` is greater than 32. +/// * `DataPackingError::NotEnoughBits` - If `offset + size` exceeds 64 bits. +pub fn write_nbit_u32( + data: &mut u64, + offset: u32, + value: u32, + size: u8, +) -> Result<(), DataPackingError> { + if size > 32 { + return Err(DataPackingError::SizeExceedsMaxSize(size, 32)); + } + if offset + size as u32 > 64 { + return Err(DataPackingError::NotEnoughBits(size, offset)); + } + let mask = (1 << size) - 1; + *data &= !((mask as u64) << offset); + *data |= ((value as u64) & mask) << offset; + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + /// Tests the `read_nbit_u32` function with various inputs. + #[test] + fn test_read_nbit_u32() { + let data: u64 = 0b110101011; + assert_eq!(read_nbit_u32(&data, 3, 0).unwrap(), 0b011); + assert_eq!(read_nbit_u32(&data, 3, 3).unwrap(), 0b101); + assert_eq!(read_nbit_u32(&data, 3, 6).unwrap(), 0b110); + assert_eq!(read_nbit_u32(&data, 3, 9).unwrap(), 0b000); + } + + /// Tests the `write_nbit_u32` function with various inputs. + #[test] + fn test_write_nbit_u32() { + let mut data: u64 = 0; + write_nbit_u32(&mut data, 0, 0b011, 3).unwrap(); + assert_eq!(data, 0b011); + write_nbit_u32(&mut data, 3, 0b101, 3).unwrap(); + assert_eq!(data, 0b101011); + write_nbit_u32(&mut data, 6, 0b110, 3).unwrap(); + assert_eq!(data, 0b110101011); + write_nbit_u32(&mut data, 9, 0b000, 3).unwrap(); + assert_eq!(data, 0b110101011); + } +} diff --git a/src/lib/utils/general_purpose/src/data_packing/u8.rs b/src/lib/utils/general_purpose/src/data_packing/u8.rs new file mode 100644 index 00000000..a05b04ae --- /dev/null +++ b/src/lib/utils/general_purpose/src/data_packing/u8.rs @@ -0,0 +1,92 @@ +use crate::data_packing::errors::DataPackingError; + +/// Reads a specified number of bits from a given offset in a 64-bit unsigned integer. +/// +/// # Arguments +/// +/// * `data` - A reference to the 64-bit unsigned integer to read from. +/// * `size` - The number of bits to read (must be 8 or less). +/// * `offset` - The bit offset from which to start reading. +/// +/// # Returns +/// +/// * `Ok(u8)` - The extracted bits as an 8-bit unsigned integer. +/// * `Err(DataPackingError)` - If the size exceeds 8 bits or the offset plus size exceeds 64 bits. +/// +/// # Errors +/// +/// * `DataPackingError::SizeExceedsMaxSize` - If `size` is greater than 8. +/// * `DataPackingError::NotEnoughBits` - If `offset + size` exceeds 64 bits. +pub fn read_nbit_u8(data: &i64, size: u8, offset: u32) -> Result { + if size > 8 { + return Err(DataPackingError::SizeExceedsMaxSize(size, 8)); + } + if offset + size as u32 > 64 { + return Err(DataPackingError::NotEnoughBits(size, offset)); + } + Ok(((data >> offset) & ((1 << size) - 1)) as u8) +} + +/// Writes a specified number of bits to a given offset in a 64-bit unsigned integer. +/// +/// # Arguments +/// +/// * `data` - A mutable reference to the 64-bit unsigned integer to write to. +/// * `offset` - The bit offset from which to start writing. +/// * `value` - The 8-bit unsigned integer value to write. +/// * `size` - The number of bits to write (must be 8 or less). +/// +/// # Returns +/// +/// * `Ok(())` - If the bits were successfully written. +/// * `Err(DataPackingError)` - If the size exceeds 8 bits or the offset plus size exceeds 64 bits. +/// +/// # Errors +/// +/// * `DataPackingError::SizeExceedsMaxSize` - If `size` is greater than 8. +/// * `DataPackingError::NotEnoughBits` - If `offset + size` exceeds 64 bits. +pub fn write_nbit_u8( + data: &mut u64, + offset: u32, + value: u8, + size: u8, +) -> Result<(), DataPackingError> { + if size > 8 { + return Err(DataPackingError::SizeExceedsMaxSize(size, 8)); + } + if offset + size as u32 > 64 { + return Err(DataPackingError::NotEnoughBits(size, offset)); + } + *data &= !(((1 << size) - 1) << offset); + *data |= (value as u64) << offset; + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + /// Tests the `read_nbit_u8` function with various inputs. + #[test] + fn test_read_nbit_u8() { + let data: i64 = 0b110101011; + assert_eq!(read_nbit_u8(&data, 3, 0).unwrap(), 0b011); + assert_eq!(read_nbit_u8(&data, 3, 3).unwrap(), 0b101); + assert_eq!(read_nbit_u8(&data, 3, 6).unwrap(), 0b110); + assert_eq!(read_nbit_u8(&data, 3, 9).unwrap(), 0b000); + } + + /// Tests the `write_nbit_u8` function with various inputs. + #[test] + fn test_write_nbit_u8() { + let mut data: u64 = 0; + write_nbit_u8(&mut data, 0, 0b011, 3).unwrap(); + assert_eq!(data, 0b011); + write_nbit_u8(&mut data, 3, 0b101, 3).unwrap(); + assert_eq!(data, 0b101011); + write_nbit_u8(&mut data, 6, 0b110, 3).unwrap(); + assert_eq!(data, 0b110101011); + write_nbit_u8(&mut data, 9, 0b000, 3).unwrap(); + assert_eq!(data, 0b110101011); + } +} diff --git a/src/lib/utils/general_purpose/src/lib.rs b/src/lib/utils/general_purpose/src/lib.rs index 3dc1a972..4ee317f8 100644 --- a/src/lib/utils/general_purpose/src/lib.rs +++ b/src/lib/utils/general_purpose/src/lib.rs @@ -1,3 +1,4 @@ pub mod hashing; pub mod paths; pub mod simd; +pub mod data_packing; diff --git a/src/lib/world/src/chunk_format.rs b/src/lib/world/src/chunk_format.rs index ea6210b5..f063dbdb 100644 --- a/src/lib/world/src/chunk_format.rs +++ b/src/lib/world/src/chunk_format.rs @@ -9,7 +9,7 @@ use lazy_static::lazy_static; use std::collections::HashMap; use std::io::Read; use tracing::error; -use vanilla_chunk_format::Palette; +use vanilla_chunk_format::BlockData; #[cfg(test)] const BLOCKSFILE: &[u8] = &[0]; @@ -23,17 +23,17 @@ const BLOCKSFILE: &[u8] = &[0]; const BLOCKSFILE: &[u8] = include_bytes!("../../../../.etc/blockmappings.bz2"); lazy_static! { - static ref ID2BLOCK: HashMap = { + pub static ref ID2BLOCK: HashMap = { let mut bzipreader = bzip2::read::BzDecoder::new(BLOCKSFILE); let mut output = String::new(); bzipreader.read_to_string(&mut output).unwrap(); - let string_keys: HashMap = serde_json::from_str(&output).unwrap(); + let string_keys: HashMap = serde_json::from_str(&output).unwrap(); string_keys .iter() .map(|(k, v)| (k.parse::().unwrap(), v.clone())) .collect() }; - static ref BLOCK2ID: HashMap = + pub static ref BLOCK2ID: HashMap = ID2BLOCK.iter().map(|(k, v)| (v.clone(), *k)).collect(); } @@ -72,7 +72,7 @@ pub struct BlockStates { pub palette: Vec, } -fn convert_to_net_palette(vanilla_palettes: Vec) -> Result, WorldError> { +fn convert_to_net_palette(vanilla_palettes: Vec) -> Result, WorldError> { let mut new_palette = Vec::new(); for palette in vanilla_palettes { if let Some(id) = BLOCK2ID.get(&palette) { diff --git a/src/lib/world/src/edits.rs b/src/lib/world/src/edits.rs new file mode 100644 index 00000000..cdb68570 --- /dev/null +++ b/src/lib/world/src/edits.rs @@ -0,0 +1,43 @@ +use crate::errors::WorldError; +use crate::vanilla_chunk_format::BlockData; +use crate::World; + +impl World { + pub async fn get_block( + &self, + x: i32, + y: i32, + z: i32, + dimension: &str, + ) -> Result { + let chunk_x = x / 16; + let chunk_z = z / 16; + let chunk = self.load_chunk(chunk_x, chunk_z, dimension).await?; + let section = chunk + .sections + .iter() + .find(|section| section.y == (y / 16) as i8) + .ok_or(WorldError::SectionOutOfBounds(y / 16))?; + let bits_per_block = section.block_states.bits_per_block as usize; + let data = §ion.block_states.data; + // for some reason the y is off by one block + let index = ((y) % 16) * 256 + (z % 16) * 16 + (x % 16); + let i64_index = (index * bits_per_block as i32) as usize / 64; + let packed_u64 = data.get(i64_index).ok_or(WorldError::ChunkNotFound)?; + let offset = (index as usize * bits_per_block) % 64; + let id = ferrumc_general_purpose::data_packing::u32::read_nbit_u32( + packed_u64, + bits_per_block as u8, + offset as u32, + )?; + let palette_id = section + .block_states + .palette + .get(id as usize) + .ok_or(WorldError::ChunkNotFound)?; + Ok(crate::chunk_format::ID2BLOCK + .get(&palette_id.val) + .unwrap_or(&BlockData::default()) + .clone()) + } +} diff --git a/src/lib/world/src/errors.rs b/src/lib/world/src/errors.rs index 9119e249..85b9b183 100644 --- a/src/lib/world/src/errors.rs +++ b/src/lib/world/src/errors.rs @@ -1,10 +1,11 @@ use crate::errors::WorldError::{GenericIOError, PermissionError}; -use crate::vanilla_chunk_format::Palette; +use crate::vanilla_chunk_format::BlockData; use errors::AnvilError; use ferrumc_anvil::errors; use ferrumc_storage::errors::StorageError; use std::io::ErrorKind; use thiserror::Error; +use ferrumc_general_purpose::data_packing::errors::DataPackingError; #[derive(Debug, Error)] pub enum WorldError { @@ -35,9 +36,13 @@ pub enum WorldError { #[error("Anvil Decode Error: {0}")] AnvilDecodeError(AnvilError), #[error("Missing block mapping: {0}")] - MissingBlockMapping(Palette), + MissingBlockMapping(BlockData), #[error("Invalid memory map size: {0}")] InvalidMapSize(u64), + #[error("Section out of bounds: {0}")] + SectionOutOfBounds(i32), + #[error("Invalid block state data: {0}")] + InvalidBlockStateData(#[source] DataPackingError), } impl From for WorldError { @@ -61,3 +66,9 @@ impl From for WorldError { WorldError::AnvilDecodeError(err) } } + +impl From for WorldError { + fn from(err: DataPackingError) -> Self { + WorldError::InvalidBlockStateData(err) + } +} diff --git a/src/lib/world/src/lib.rs b/src/lib/world/src/lib.rs index d4736b95..b371ba8b 100644 --- a/src/lib/world/src/lib.rs +++ b/src/lib/world/src/lib.rs @@ -5,6 +5,7 @@ mod db_functions; pub mod errors; mod importing; mod vanilla_chunk_format; +pub mod edits; use crate::chunk_format::Chunk; use crate::errors::WorldError; diff --git a/src/lib/world/src/vanilla_chunk_format.rs b/src/lib/world/src/vanilla_chunk_format.rs index 5cdfb149..4f25a758 100644 --- a/src/lib/world/src/vanilla_chunk_format.rs +++ b/src/lib/world/src/vanilla_chunk_format.rs @@ -95,24 +95,33 @@ pub(crate) struct Section { #[derive(deepsize::DeepSizeOf)] pub(crate) struct BlockStates { pub data: Option>, - pub palette: Option>, + pub palette: Option>, } #[apply(ChunkDerives)] #[derive(deepsize::DeepSizeOf, Hash)] -pub struct Palette { +pub struct BlockData { #[nbt(rename = "Name")] pub name: String, #[nbt(rename = "Properties")] pub properties: Option>, } -impl Display for Palette { +impl Display for BlockData { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.name) } } +impl Default for BlockData { + fn default() -> Self { + BlockData { + name: String::from("minecraft:air"), + properties: None, + } + } +} + #[apply(ChunkDerives)] #[derive(deepsize::DeepSizeOf)] pub(crate) struct Properties { From 2c8841058d5a877b343674c68e94e2483747613e Mon Sep 17 00:00:00 2001 From: ReCore Date: Tue, 17 Dec 2024 14:48:21 +1030 Subject: [PATCH 02/21] now works more reliably --- src/bin/src/packet_handlers/login_process.rs | 5 ---- .../transform/update_player_position.rs | 15 ++++++++++- .../general_purpose/src/data_packing/i16.rs | 4 +-- .../general_purpose/src/data_packing/i8.rs | 2 +- .../general_purpose/src/data_packing/u16.rs | 2 +- .../general_purpose/src/data_packing/u32.rs | 4 +-- src/lib/utils/general_purpose/src/lib.rs | 2 +- src/lib/world/src/edits.rs | 27 ++++++++++++------- src/lib/world/src/errors.rs | 10 +++---- src/lib/world/src/lib.rs | 2 +- 10 files changed, 45 insertions(+), 28 deletions(-) diff --git a/src/bin/src/packet_handlers/login_process.rs b/src/bin/src/packet_handlers/login_process.rs index 3806fcc5..748a4b33 100644 --- a/src/bin/src/packet_handlers/login_process.rs +++ b/src/bin/src/packet_handlers/login_process.rs @@ -177,11 +177,6 @@ async fn handle_ack_finish_configuration( let mut chunk_recv = state.universe.get_mut::(conn_id)?; chunk_recv.last_chunk = Some((pos.x as i32, pos.z as i32, String::from("overworld"))); chunk_recv.calculate_chunks().await; - - debug!( - "Block: {}", - state.world.get_block(1, 163, 2, "overworld").await.unwrap() - ); send_keep_alive(conn_id, state, &mut writer).await?; Ok(ack_finish_configuration_event) diff --git a/src/bin/src/packet_handlers/transform/update_player_position.rs b/src/bin/src/packet_handlers/transform/update_player_position.rs index 6487e68f..f5cccefe 100644 --- a/src/bin/src/packet_handlers/transform/update_player_position.rs +++ b/src/bin/src/packet_handlers/transform/update_player_position.rs @@ -7,7 +7,7 @@ use ferrumc_net::errors::NetError; use ferrumc_net::packets::packet_events::TransformEvent; use ferrumc_net::utils::ecs_helpers::EntityExt; use ferrumc_state::GlobalState; -use tracing::trace; +use tracing::{debug, trace}; #[event_handler] async fn handle_player_move( @@ -16,6 +16,19 @@ async fn handle_player_move( ) -> Result { let conn_id = event.conn_id; if let Some(ref new_position) = event.position { + debug!( + "Block: {}", + state + .world + .get_block( + new_position.x as i32, + new_position.y as i32 - 1, + new_position.z as i32, + "overworld" + ) + .await + .unwrap() + ); trace!("Getting chunk_recv 1 for player move"); let mut chunk_recv = state.universe.get_mut::(conn_id)?; trace!("Got chunk_recv 1 for player move"); diff --git a/src/lib/utils/general_purpose/src/data_packing/i16.rs b/src/lib/utils/general_purpose/src/data_packing/i16.rs index d0131f7d..e63e62c0 100644 --- a/src/lib/utils/general_purpose/src/data_packing/i16.rs +++ b/src/lib/utils/general_purpose/src/data_packing/i16.rs @@ -66,7 +66,7 @@ pub fn write_nbit_i16( return Err(DataPackingError::NotEnoughBits(size, offset)); } let mask = (1 << size) - 1; - *data &= !((mask as i64) << offset); + *data &= !(mask << offset); *data |= ((value as i64) & mask) << offset; Ok(()) } @@ -98,4 +98,4 @@ mod tests { write_nbit_i16(&mut data, 9, 0b000, 3).unwrap(); assert_eq!(data, 0b110101011); } -} \ No newline at end of file +} diff --git a/src/lib/utils/general_purpose/src/data_packing/i8.rs b/src/lib/utils/general_purpose/src/data_packing/i8.rs index f95679a5..310d72e5 100644 --- a/src/lib/utils/general_purpose/src/data_packing/i8.rs +++ b/src/lib/utils/general_purpose/src/data_packing/i8.rs @@ -66,7 +66,7 @@ pub fn write_nbit_i8( return Err(DataPackingError::NotEnoughBits(size, offset)); } let mask = (1 << size) - 1; - *data &= !((mask as i64) << offset); + *data &= !((mask) << offset); *data |= ((value as i64) & mask) << offset; Ok(()) } diff --git a/src/lib/utils/general_purpose/src/data_packing/u16.rs b/src/lib/utils/general_purpose/src/data_packing/u16.rs index 9bc8c06d..e697bc8b 100644 --- a/src/lib/utils/general_purpose/src/data_packing/u16.rs +++ b/src/lib/utils/general_purpose/src/data_packing/u16.rs @@ -58,7 +58,7 @@ pub fn write_nbit_u16( return Err(DataPackingError::NotEnoughBits(size, offset)); } let mask = (1 << size) - 1; - *data &= !((mask as u64) << offset); + *data &= !((mask) << offset); *data |= ((value as u64) & mask) << offset; Ok(()) } diff --git a/src/lib/utils/general_purpose/src/data_packing/u32.rs b/src/lib/utils/general_purpose/src/data_packing/u32.rs index 6e498ebf..cc9d16cb 100644 --- a/src/lib/utils/general_purpose/src/data_packing/u32.rs +++ b/src/lib/utils/general_purpose/src/data_packing/u32.rs @@ -58,7 +58,7 @@ pub fn write_nbit_u32( return Err(DataPackingError::NotEnoughBits(size, offset)); } let mask = (1 << size) - 1; - *data &= !((mask as u64) << offset); + *data &= !((mask) << offset); *data |= ((value as u64) & mask) << offset; Ok(()) } @@ -70,7 +70,7 @@ mod tests { /// Tests the `read_nbit_u32` function with various inputs. #[test] fn test_read_nbit_u32() { - let data: u64 = 0b110101011; + let data: i64 = 0b110101011; assert_eq!(read_nbit_u32(&data, 3, 0).unwrap(), 0b011); assert_eq!(read_nbit_u32(&data, 3, 3).unwrap(), 0b101); assert_eq!(read_nbit_u32(&data, 3, 6).unwrap(), 0b110); diff --git a/src/lib/utils/general_purpose/src/lib.rs b/src/lib/utils/general_purpose/src/lib.rs index 4ee317f8..d6eb177f 100644 --- a/src/lib/utils/general_purpose/src/lib.rs +++ b/src/lib/utils/general_purpose/src/lib.rs @@ -1,4 +1,4 @@ +pub mod data_packing; pub mod hashing; pub mod paths; pub mod simd; -pub mod data_packing; diff --git a/src/lib/world/src/edits.rs b/src/lib/world/src/edits.rs index cdb68570..e19a7113 100644 --- a/src/lib/world/src/edits.rs +++ b/src/lib/world/src/edits.rs @@ -1,3 +1,4 @@ +use crate::chunk_format::ID2BLOCK; use crate::errors::WorldError; use crate::vanilla_chunk_format::BlockData; use crate::World; @@ -10,21 +11,29 @@ impl World { z: i32, dimension: &str, ) -> Result { - let chunk_x = x / 16; - let chunk_z = z / 16; + let chunk_x = x >> 4; + let chunk_z = z >> 4; let chunk = self.load_chunk(chunk_x, chunk_z, dimension).await?; let section = chunk .sections .iter() - .find(|section| section.y == (y / 16) as i8) - .ok_or(WorldError::SectionOutOfBounds(y / 16))?; + .find(|section| section.y == (y >> 4) as i8) + .ok_or(WorldError::SectionOutOfBounds(y >> 4))?; + if section.block_states.palette.len() == 1 { + return ID2BLOCK + .get(§ion.block_states.palette[0].val) + .cloned() + .ok_or(WorldError::ChunkNotFound); + } let bits_per_block = section.block_states.bits_per_block as usize; let data = §ion.block_states.data; - // for some reason the y is off by one block - let index = ((y) % 16) * 256 + (z % 16) * 16 + (x % 16); - let i64_index = (index * bits_per_block as i32) as usize / 64; - let packed_u64 = data.get(i64_index).ok_or(WorldError::ChunkNotFound)?; - let offset = (index as usize * bits_per_block) % 64; + let blocks_per_i64 = (64f64 / bits_per_block as f64).floor() as usize; + let index = ((y & 0xf) * 256 + (z & 0xf) * 16 + (x & 0xf)) as usize; + let i64_index = index / blocks_per_i64; + let packed_u64 = data + .get(i64_index) + .ok_or(WorldError::InvalidBlockStateData())?; + let offset = (index % blocks_per_i64) * bits_per_block; let id = ferrumc_general_purpose::data_packing::u32::read_nbit_u32( packed_u64, bits_per_block as u8, diff --git a/src/lib/world/src/errors.rs b/src/lib/world/src/errors.rs index 85b9b183..8f768611 100644 --- a/src/lib/world/src/errors.rs +++ b/src/lib/world/src/errors.rs @@ -2,10 +2,10 @@ use crate::errors::WorldError::{GenericIOError, PermissionError}; use crate::vanilla_chunk_format::BlockData; use errors::AnvilError; use ferrumc_anvil::errors; +use ferrumc_general_purpose::data_packing::errors::DataPackingError; use ferrumc_storage::errors::StorageError; use std::io::ErrorKind; use thiserror::Error; -use ferrumc_general_purpose::data_packing::errors::DataPackingError; #[derive(Debug, Error)] pub enum WorldError { @@ -41,8 +41,8 @@ pub enum WorldError { InvalidMapSize(u64), #[error("Section out of bounds: {0}")] SectionOutOfBounds(i32), - #[error("Invalid block state data: {0}")] - InvalidBlockStateData(#[source] DataPackingError), + #[error("Invalid block state data")] + InvalidBlockStateData(), } impl From for WorldError { @@ -68,7 +68,7 @@ impl From for WorldError { } impl From for WorldError { - fn from(err: DataPackingError) -> Self { - WorldError::InvalidBlockStateData(err) + fn from(_: DataPackingError) -> Self { + WorldError::InvalidBlockStateData() } } diff --git a/src/lib/world/src/lib.rs b/src/lib/world/src/lib.rs index b371ba8b..52a00b03 100644 --- a/src/lib/world/src/lib.rs +++ b/src/lib/world/src/lib.rs @@ -2,10 +2,10 @@ pub mod chunk_format; mod db_functions; +pub mod edits; pub mod errors; mod importing; mod vanilla_chunk_format; -pub mod edits; use crate::chunk_format::Chunk; use crate::errors::WorldError; From 5efed7e659ab86ea6b25200296033761659ff0ba Mon Sep 17 00:00:00 2001 From: ReCore Date: Tue, 17 Dec 2024 16:01:42 +1030 Subject: [PATCH 03/21] Block reading fixed --- .../transform/update_player_position.rs | 6 +++--- .../general_purpose/src/data_packing/u32.rs | 2 +- src/lib/world/src/chunk_format.rs | 3 ++- src/lib/world/src/edits.rs | 19 +++++++++++++++++++ 4 files changed, 25 insertions(+), 5 deletions(-) diff --git a/src/bin/src/packet_handlers/transform/update_player_position.rs b/src/bin/src/packet_handlers/transform/update_player_position.rs index f5cccefe..01d7f976 100644 --- a/src/bin/src/packet_handlers/transform/update_player_position.rs +++ b/src/bin/src/packet_handlers/transform/update_player_position.rs @@ -21,9 +21,9 @@ async fn handle_player_move( state .world .get_block( - new_position.x as i32, - new_position.y as i32 - 1, - new_position.z as i32, + new_position.x.floor() as i32, + new_position.y.floor() as i32 - 1, + new_position.z.floor() as i32, "overworld" ) .await diff --git a/src/lib/utils/general_purpose/src/data_packing/u32.rs b/src/lib/utils/general_purpose/src/data_packing/u32.rs index cc9d16cb..91e2ed48 100644 --- a/src/lib/utils/general_purpose/src/data_packing/u32.rs +++ b/src/lib/utils/general_purpose/src/data_packing/u32.rs @@ -24,7 +24,7 @@ pub fn read_nbit_u32(data: &i64, size: u8, offset: u32) -> Result 64 { return Err(DataPackingError::NotEnoughBits(size, offset)); } - Ok(((data >> offset) & ((1 << size) - 1)) as u32) + Ok(((*data as u64 >> offset as u64) & ((1u64 << size) - 1u64)) as u32) } /// Writes a specified number of bits to a given offset in a 64-bit unsigned integer. diff --git a/src/lib/world/src/chunk_format.rs b/src/lib/world/src/chunk_format.rs index f063dbdb..6c0e9f6d 100644 --- a/src/lib/world/src/chunk_format.rs +++ b/src/lib/world/src/chunk_format.rs @@ -6,6 +6,7 @@ use deepsize::DeepSizeOf; use ferrumc_macros::{NBTDeserialize, NBTSerialize}; use ferrumc_net_codec::net_types::var_int::VarInt; use lazy_static::lazy_static; +use std::cmp::max; use std::collections::HashMap; use std::io::Read; use tracing::error; @@ -126,7 +127,7 @@ impl VanillaChunk { .map_or(vec![], |biome_data| biome_data.palette.clone()); let non_air_blocks = palette.iter().filter(|id| id.name != "air").count() as u16; let block_states = BlockStates { - bits_per_block: (palette.len() as f32).log2().ceil() as u8, + bits_per_block: max((palette.len() as f32).log2().ceil() as u8, 4), non_air_blocks, data: block_data, palette: convert_to_net_palette(palette)?, diff --git a/src/lib/world/src/edits.rs b/src/lib/world/src/edits.rs index e19a7113..5bdd0303 100644 --- a/src/lib/world/src/edits.rs +++ b/src/lib/world/src/edits.rs @@ -4,6 +4,25 @@ use crate::vanilla_chunk_format::BlockData; use crate::World; impl World { + /// Asynchronously retrieves the block data at the specified coordinates in the given dimension. + /// + /// # Arguments + /// + /// * `x` - The x-coordinate of the block. + /// * `y` - The y-coordinate of the block. + /// * `z` - The z-coordinate of the block. + /// * `dimension` - The dimension in which the block is located. + /// + /// # Returns + /// + /// * `Ok(BlockData)` - The block data at the specified coordinates. + /// * `Err(WorldError)` - If an error occurs while retrieving the block data. + /// + /// # Errors + /// + /// * `WorldError::SectionOutOfBounds` - If the section containing the block is out of bounds. + /// * `WorldError::ChunkNotFound` - If the chunk or block data is not found. + /// * `WorldError::InvalidBlockStateData` - If the block state data is invalid. pub async fn get_block( &self, x: i32, From af982e246c8153a619efa44ec858496cf8efeac1 Mon Sep 17 00:00:00 2001 From: ReCore Date: Fri, 20 Dec 2024 15:44:55 +1030 Subject: [PATCH 04/21] Block count tracking + dependency cleanup --- Cargo.toml | 8 +++----- src/lib/storage/src/lmdb.rs | 1 - src/lib/world/Cargo.toml | 4 ---- src/lib/world/src/chunk_format.rs | 32 +++++++++++++++++++++++++++++-- src/lib/world/src/importing.rs | 9 ++++++--- 5 files changed, 39 insertions(+), 15 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4a532388..c5dbb764 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -148,7 +148,6 @@ hashbrown = "0.15.0" tinyvec = "1.8.0" dashmap = "6.1.0" uuid = { version = "1.1", features = ["v4", "v3", "serde"] } -whirlwind = "0.1.1" # Macros lazy_static = "1.5.0" @@ -168,12 +167,12 @@ libflate = "2.1.0" flate2 = { version = "1.0.33", features = ["zlib"], default-features = false } zstd = { version = "0.13.2" } brotli = "7.0.0" -lzzzz = "1.1.0" +lzzzz = "2.0.0" yazi = "0.2.0" -bzip2 = "0.4.1" +bzip2 = "0.5.0" # Database -heed = "0.20.5" +heed = "0.21.0" moka = "0.12.8" # CLI @@ -186,7 +185,6 @@ deepsize = "0.2.0" page_size = "0.6.0" # I/O -tempfile = "3.12.0" memmap2 = "0.9.5" # Benchmarking diff --git a/src/lib/storage/src/lmdb.rs b/src/lib/storage/src/lmdb.rs index bc9c5960..7988a586 100644 --- a/src/lib/storage/src/lmdb.rs +++ b/src/lib/storage/src/lmdb.rs @@ -18,7 +18,6 @@ impl From for StorageError { Error::Io(e) => StorageError::GenericIoError(e), Error::Encoding(e) => StorageError::WriteError(e.to_string()), Error::Decoding(e) => StorageError::ReadError(e.to_string()), - Error::DatabaseClosing => StorageError::CloseError("Database closing".to_string()), _ => StorageError::DatabaseError(err.to_string()), } } diff --git a/src/lib/world/Cargo.toml b/src/lib/world/Cargo.toml index 2ccc3a3f..03b0089e 100644 --- a/src/lib/world/Cargo.toml +++ b/src/lib/world/Cargo.toml @@ -6,9 +6,6 @@ edition = "2021" [dependencies] thiserror = { workspace = true } - -ferrumc-logging = { workspace = true } -ferrumc-profiling = { workspace = true } ferrumc-storage = { workspace = true } ferrumc-config = { workspace = true } tracing = { workspace = true } @@ -31,4 +28,3 @@ serde_json = { workspace = true } indicatif = { workspace = true } wyhash = { workspace = true } moka = { workspace = true, features = ["future"] } -log = "0.4.22" diff --git a/src/lib/world/src/chunk_format.rs b/src/lib/world/src/chunk_format.rs index 6c0e9f6d..21a8d563 100644 --- a/src/lib/world/src/chunk_format.rs +++ b/src/lib/world/src/chunk_format.rs @@ -71,6 +71,7 @@ pub struct BlockStates { pub non_air_blocks: u16, pub data: Vec, pub palette: Vec, + pub block_counts: HashMap, } fn convert_to_net_palette(vanilla_palettes: Vec) -> Result, WorldError> { @@ -125,9 +126,36 @@ impl VanillaChunk { .biomes .as_ref() .map_or(vec![], |biome_data| biome_data.palette.clone()); - let non_air_blocks = palette.iter().filter(|id| id.name != "air").count() as u16; + let bits_per_block = max((palette.len() as f32).log2().ceil() as u8, 4); + let mut block_counts = HashMap::new(); + for chunk in &block_data { + let mut i = 0; + while i + bits_per_block < 64 { + let id = ferrumc_general_purpose::data_packing::i32::read_nbit_i32( + chunk, + bits_per_block, + i as u32, + )?; + *block_counts.entry(id).or_insert(0) += 1; + i += bits_per_block; + } + } + if block_data.is_empty() { + let single_block = if let Some(block) = palette.first() { + if let Some(id) = BLOCK2ID.get(block) { + *id + } else { + 0 + } + } else { + 0 + }; + block_counts.insert(single_block, 4096); + } + let non_air_blocks = 4096 - *block_counts.get(&0).unwrap_or(&0) as u16; let block_states = BlockStates { - bits_per_block: max((palette.len() as f32).log2().ceil() as u8, 4), + bits_per_block, + block_counts, non_air_blocks, data: block_data, palette: convert_to_net_palette(palette)?, diff --git a/src/lib/world/src/importing.rs b/src/lib/world/src/importing.rs index d2ba641a..2869b108 100644 --- a/src/lib/world/src/importing.rs +++ b/src/lib/world/src/importing.rs @@ -123,15 +123,18 @@ impl World { let cloned_progress_bar = progress_bar.clone(); let self_clone = self.clone(); task_set.spawn(async move { - if let Ok(chunk) = vanilla_chunk.to_custom_format() { + match vanilla_chunk.to_custom_format() { + Ok(chunk) => { if let Err(e) = save_chunk_internal(&self_clone, chunk).await { error!("Could not save chunk: {}", e); } else { cloned_progress_bar.inc(1); } - } else { - error!("Could not convert chunk to custom format: {:?}", chunk); } + Err(e) => { + error!("Could not convert chunk to custom format: {}", e); + } + } }); } Err(e) => { From 26f30d55d36b727262d8bfe61b9a287116d75542 Mon Sep 17 00:00:00 2001 From: ReCore Date: Fri, 20 Dec 2024 16:16:33 +1030 Subject: [PATCH 05/21] Added packet generator script --- scripts/new_packet.py | 64 +++++++++++++++++++ src/lib/net/src/packets/incoming/mod.rs | 2 + .../net/src/packets/incoming/place_block.rs | 17 +++++ 3 files changed, 83 insertions(+) create mode 100644 scripts/new_packet.py create mode 100644 src/lib/net/src/packets/incoming/place_block.rs diff --git a/scripts/new_packet.py b/scripts/new_packet.py new file mode 100644 index 00000000..0f493608 --- /dev/null +++ b/scripts/new_packet.py @@ -0,0 +1,64 @@ +import os.path + +incoming_template = """ +use crate::packets::IncomingPacket; +use crate::NetResult; +use ferrumc_macros::{packet, NetDecode}; +use ferrumc_state::ServerState; +use std::sync::Arc; + +#[derive(NetDecode)] +#[packet(packet_id = ++id++, state = "play")] +pub struct ++name++ { +} + +impl IncomingPacket for ++name++ { + async fn handle(self, conn_id: usize, state: Arc) -> NetResult<()> { + todo!() + } +} +""" + +outgoing_template = """ +use ferrumc_macros::{packet, NetEncode};\ +use std::io::Write; + +#[derive(NetEncode)] +#[packet(packet_id = ++id++)] +pub struct ++name++ {} +""" + + +def to_snake_case(string) -> str: + return string.lower().replace(" ", "_") + + +def to_camel_case(string) -> str: + return string.title().replace(" ", "") + + +packet_type_input = input("Incoming or outgoing packet? (i/o): ") +packet_type = "" +if packet_type_input == "i": + packet_type = "incoming" +elif packet_type_input == "o": + packet_type = "outgoing" +else: + print("Invalid input") + exit() + +packet_name = input("Packet name: ") +packets_dir = os.path.join(os.path.join(os.path.dirname(__file__), ".."), "src/lib/net/src/packets") + +packet_id = input("Packet ID (formatted like 0x01): ") +packet_id = packet_id[:-2] + packet_id[-2:].upper() + +with open(f"{packets_dir}/{packet_type}/{to_snake_case(packet_name)}.rs", "x") as f: + if packet_type == "incoming": + f.write(incoming_template.replace("++name++", to_camel_case(packet_name)).replace("++id++", packet_id)) + with open(f"{packets_dir}/incoming/mod.rs", "a") as modfile: + modfile.write(f"\npub mod {to_snake_case(packet_name)};") + else: + f.write(outgoing_template.replace("++name++", to_camel_case(packet_name)).replace("++id++", packet_id)) + with open(f"{packets_dir}/outgoing/mod.rs", "a") as modfile: + modfile.write(f"\npub mod {to_snake_case(packet_name)};") diff --git a/src/lib/net/src/packets/incoming/mod.rs b/src/lib/net/src/packets/incoming/mod.rs index 2f5254a1..a857e372 100644 --- a/src/lib/net/src/packets/incoming/mod.rs +++ b/src/lib/net/src/packets/incoming/mod.rs @@ -14,3 +14,5 @@ pub mod packet_skeleton; pub mod set_player_position; pub mod set_player_position_and_rotation; pub mod set_player_rotation; + +pub mod place_block; \ No newline at end of file diff --git a/src/lib/net/src/packets/incoming/place_block.rs b/src/lib/net/src/packets/incoming/place_block.rs new file mode 100644 index 00000000..fae22682 --- /dev/null +++ b/src/lib/net/src/packets/incoming/place_block.rs @@ -0,0 +1,17 @@ + +use crate::packets::IncomingPacket; +use crate::NetResult; +use ferrumc_macros::{packet, NetDecode}; +use ferrumc_state::ServerState; +use std::sync::Arc; + +#[derive(NetDecode)] +#[packet(packet_id = 0x3C, state = "play")] +pub struct PlaceBlock { +} + +impl IncomingPacket for PlaceBlock { + async fn handle(self, conn_id: usize, state: Arc) -> NetResult<()> { + todo!() + } +} From 8ad1965aa64ab0522646d352acbd16083110107c Mon Sep 17 00:00:00 2001 From: ReCore Date: Tue, 31 Dec 2024 16:56:43 +1030 Subject: [PATCH 06/21] Now correctly resizes sections --- src/bin/Cargo.toml | 1 + src/bin/src/main.rs | 1 + .../transform/update_player_position.rs | 13 --- src/bin/src/systems/chunk_sender.rs | 32 ++++-- src/lib/core/src/chunks/chunk_receiver.rs | 2 + .../codec/src/net_types/network_position.rs | 22 +++- .../src/packets/incoming/chunks_per_tick.rs | 20 ++++ src/lib/net/src/packets/incoming/mod.rs | 1 + .../net/src/packets/incoming/place_block.rs | 19 +++- src/lib/net/src/utils/broadcast.rs | 3 +- .../general_purpose/src/data_packing/i32.rs | 34 +++--- src/lib/world/src/chunk_format.rs | 101 ++++++++++++++++-- src/lib/world/src/edits.rs | 5 +- src/lib/world/src/errors.rs | 6 +- 14 files changed, 207 insertions(+), 53 deletions(-) create mode 100644 src/lib/net/src/packets/incoming/chunks_per_tick.rs diff --git a/src/bin/Cargo.toml b/src/bin/Cargo.toml index 0c0aaaea..71dce6c1 100644 --- a/src/bin/Cargo.toml +++ b/src/bin/Cargo.toml @@ -36,6 +36,7 @@ futures = { workspace = true } serde_json = { workspace = true } async-trait = { workspace = true } clap = { workspace = true, features = ["derive"] } +rand = "0.9.0-beta.1" [[bin]] diff --git a/src/bin/src/main.rs b/src/bin/src/main.rs index aca67b80..d8436910 100644 --- a/src/bin/src/main.rs +++ b/src/bin/src/main.rs @@ -1,5 +1,6 @@ #![feature(portable_simd)] #![forbid(unsafe_code)] +#![feature(random)] extern crate core; use crate::errors::BinaryError; diff --git a/src/bin/src/packet_handlers/transform/update_player_position.rs b/src/bin/src/packet_handlers/transform/update_player_position.rs index 7debcade..cb937152 100644 --- a/src/bin/src/packet_handlers/transform/update_player_position.rs +++ b/src/bin/src/packet_handlers/transform/update_player_position.rs @@ -16,19 +16,6 @@ async fn handle_player_move( ) -> Result { let conn_id = event.conn_id; if let Some(ref new_position) = event.position { - debug!( - "Block: {}", - state - .world - .get_block( - new_position.x.floor() as i32, - new_position.y.floor() as i32 - 1, - new_position.z.floor() as i32, - "overworld" - ) - .await - .unwrap() - ); trace!("Getting chunk_recv 1 for player move"); { let mut chunk_recv = state.universe.get_mut::(conn_id)?; diff --git a/src/bin/src/systems/chunk_sender.rs b/src/bin/src/systems/chunk_sender.rs index 6b359bb3..ced644ab 100644 --- a/src/bin/src/systems/chunk_sender.rs +++ b/src/bin/src/systems/chunk_sender.rs @@ -10,11 +10,12 @@ use ferrumc_net::packets::outgoing::set_center_chunk::SetCenterChunk; use ferrumc_net_codec::encode::NetEncodeOpts; use ferrumc_net_codec::net_types::var_int::VarInt; use ferrumc_state::GlobalState; +use rand::random; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; use std::time::Duration; use tokio::task::JoinSet; -use tracing::{error, info, trace}; +use tracing::{debug, error, info, trace}; pub(super) struct ChunkSenderSystem { pub stop: AtomicBool, @@ -67,20 +68,26 @@ impl System for ChunkSenderSystem { centre_coords = (chunk.0, chunk.1); } } - let mut sent_chunks = 0; { - let Ok(chunk_recv) = state.universe.get::(eid) else { - trace!("A player disconnected before we could get the ChunkReceiver"); - return Ok(()); - }; - for possible_chunk in chunk_recv.needed_chunks.iter_mut() { - if let Some(chunk) = possible_chunk.pair().1 { - let key = possible_chunk.pair().0; + trace!("Getting chunk_recv 3 for sender"); + let chunk_recv = state + .universe + .get_mut::(eid) + .expect("ChunkReceiver not found"); + trace!("Got chunk_recv 3 for sender"); + for mut possible_chunk in chunk_recv.needed_chunks.iter_mut() { + if let (key, Some(chunk)) = possible_chunk.pair_mut() { + chunk.sections.iter_mut().for_each(|section| { + // if random::() < 25 { + if let Err(e) = section.block_states.resize(8) { + error!("Error resizing block states: {:?}", e); + } + // } + }); to_drop.push(key.clone()); match ChunkAndLightData::from_chunk(&chunk.clone()) { Ok(packet) => { packets.push(packet); - sent_chunks += 1; } Err(e) => { error!("Error sending chunk: {:?}", e); @@ -125,17 +132,20 @@ impl System for ChunkSenderSystem { { error!("Error sending chunk: {:?}", e); } + let mut count = 0; for packet in packets { if let Err(e) = conn.send_packet(&packet, &NetEncodeOpts::WithLength).await { error!("Error sending chunk: {:?}", e); + } else { + count += 1; } } if let Err(e) = conn .send_packet( &ChunkBatchFinish { - batch_size: VarInt::new(sent_chunks), + batch_size: VarInt::new(count), }, &NetEncodeOpts::WithLength, ) diff --git a/src/lib/core/src/chunks/chunk_receiver.rs b/src/lib/core/src/chunks/chunk_receiver.rs index dcae60c0..16ebaaf5 100644 --- a/src/lib/core/src/chunks/chunk_receiver.rs +++ b/src/lib/core/src/chunks/chunk_receiver.rs @@ -8,6 +8,7 @@ pub struct ChunkReceiver { pub can_see: DashSet<(i32, i32, String)>, pub last_update: Instant, pub last_chunk: Option<(i32, i32, String)>, + pub chunks_per_tick: f32, } impl Default for ChunkReceiver { @@ -23,6 +24,7 @@ impl ChunkReceiver { can_see: DashSet::new(), last_update: Instant::now(), last_chunk: None, + chunks_per_tick: 0.0, } } } diff --git a/src/lib/net/crates/codec/src/net_types/network_position.rs b/src/lib/net/crates/codec/src/net_types/network_position.rs index 4a745b0a..cc428ab3 100644 --- a/src/lib/net/crates/codec/src/net_types/network_position.rs +++ b/src/lib/net/crates/codec/src/net_types/network_position.rs @@ -1,8 +1,9 @@ // I have no clue why it is saving i32 and i16. There is no precision. The actual player position is saved in f32. +use crate::decode::{NetDecode, NetDecodeOpts, NetDecodeResult}; use crate::encode::{NetEncode, NetEncodeOpts, NetEncodeResult}; use std::fmt::Display; -use std::io::Write; +use std::io::{Read, Write}; use tokio::io::AsyncWrite; /// The definition of a "Position" in the Minecraft protocol. @@ -47,10 +48,29 @@ impl NetEncode for NetworkPosition { Ok(()) } } + +impl NetDecode for NetworkPosition { + fn decode(reader: &mut R, _: &NetDecodeOpts) -> NetDecodeResult { + let mut buf = [0u8; 8]; + reader.read_exact(&mut buf)?; + Ok(NetworkPosition::from_u64(u64::from_be_bytes(buf))) + } +} + impl NetworkPosition { pub fn as_u64(&self) -> u64 { ((self.x as u64 & 0x3FFFFFF) << 38) | ((self.z as u64 & 0x3FFFFFF) << 12) | (self.y as u64 & 0xFFF) } + + pub fn from_u64(val: u64) -> Self { + let mut x = (val >> 38) as i32; + let mut y = (val << 52 >> 52) as i16; + let mut z = (val << 26 >> 38) as i32; + if x >= 1 << 25 { x -= 1 << 26 } + if y >= 1 << 11 { y -= 1 << 12 } + if z >= 1 << 25 { z -= 1 << 26 } + Self { x, y, z } + } } diff --git a/src/lib/net/src/packets/incoming/chunks_per_tick.rs b/src/lib/net/src/packets/incoming/chunks_per_tick.rs new file mode 100644 index 00000000..a0f1f759 --- /dev/null +++ b/src/lib/net/src/packets/incoming/chunks_per_tick.rs @@ -0,0 +1,20 @@ +use crate::packets::IncomingPacket; +use crate::NetResult; +use ferrumc_core::chunks::chunk_receiver::ChunkReceiver; +use ferrumc_macros::{packet, NetDecode}; +use ferrumc_state::ServerState; +use std::sync::Arc; + +#[derive(NetDecode)] +#[packet(packet_id = 0x08, state = "play")] +pub struct ChunksPerTick { + chunks_per_tick: f32, +} + +impl IncomingPacket for ChunksPerTick { + async fn handle(self, conn_id: usize, state: Arc) -> NetResult<()> { + let mut chunk_recv = state.universe.get_mut::(conn_id)?; + chunk_recv.chunks_per_tick = self.chunks_per_tick; + Ok(()) + } +} diff --git a/src/lib/net/src/packets/incoming/mod.rs b/src/lib/net/src/packets/incoming/mod.rs index 94e04a8f..bfc70f9b 100644 --- a/src/lib/net/src/packets/incoming/mod.rs +++ b/src/lib/net/src/packets/incoming/mod.rs @@ -15,5 +15,6 @@ pub mod set_player_position; pub mod set_player_position_and_rotation; pub mod set_player_rotation; +pub mod chunks_per_tick; pub mod place_block; pub mod swing_arm; diff --git a/src/lib/net/src/packets/incoming/place_block.rs b/src/lib/net/src/packets/incoming/place_block.rs index fae22682..0b891a29 100644 --- a/src/lib/net/src/packets/incoming/place_block.rs +++ b/src/lib/net/src/packets/incoming/place_block.rs @@ -1,17 +1,28 @@ - use crate::packets::IncomingPacket; use crate::NetResult; use ferrumc_macros::{packet, NetDecode}; +use ferrumc_net_codec::net_types::network_position::NetworkPosition; +use ferrumc_net_codec::net_types::var_int::VarInt; use ferrumc_state::ServerState; use std::sync::Arc; +use tracing::debug; -#[derive(NetDecode)] -#[packet(packet_id = 0x3C, state = "play")] +#[derive(NetDecode, Debug)] +#[packet(packet_id = 0x38, state = "play")] pub struct PlaceBlock { + pub hand: VarInt, + pub position: NetworkPosition, + pub face: VarInt, + pub cursor_x: f32, + pub cursor_y: f32, + pub cursor_z: f32, + pub inside_block: bool, + pub sequence: VarInt, } impl IncomingPacket for PlaceBlock { async fn handle(self, conn_id: usize, state: Arc) -> NetResult<()> { - todo!() + debug!("{:?}", self); + Ok(()) } } diff --git a/src/lib/net/src/utils/broadcast.rs b/src/lib/net/src/utils/broadcast.rs index c67b068d..b2fa6468 100644 --- a/src/lib/net/src/utils/broadcast.rs +++ b/src/lib/net/src/utils/broadcast.rs @@ -2,6 +2,7 @@ use crate::connection::StreamWriter; use crate::NetResult; use async_trait::async_trait; use ferrumc_core::chunks::chunk_receiver::ChunkReceiver; +use ferrumc_core::identity::player_identity::PlayerIdentity; use ferrumc_ecs::entities::Entity; use ferrumc_net_codec::encode::{NetEncode, NetEncodeOpts}; use ferrumc_state::GlobalState; @@ -70,7 +71,7 @@ fn get_all_entities(state: &GlobalState) -> HashSet { state .universe .get_component_manager() - .get_entities_with::() + .get_entities_with::() .into_iter() .collect() } diff --git a/src/lib/utils/general_purpose/src/data_packing/i32.rs b/src/lib/utils/general_purpose/src/data_packing/i32.rs index 18d26a93..d79b10f7 100644 --- a/src/lib/utils/general_purpose/src/data_packing/i32.rs +++ b/src/lib/utils/general_purpose/src/data_packing/i32.rs @@ -17,22 +17,30 @@ use crate::data_packing::errors::DataPackingError; /// /// * `DataPackingError::SizeExceedsMaxSize` - If `size` is greater than 32. /// * `DataPackingError::NotEnoughBits` - If `offset + size` exceeds 64 bits. -pub fn read_nbit_i32(data: &i64, size: u8, offset: u32) -> Result { - if size > 32 { - return Err(DataPackingError::SizeExceedsMaxSize(size, 32)); +/// Reads an n-bit integer from a packed `i64`. +pub fn read_nbit_i32( + word: &i64, + bit_size: usize, + bit_offset: u32, +) -> Result { + if bit_size == 0 || bit_size > 32 { + return Err(DataPackingError::SizeExceedsMaxSize(bit_size as u8, 32)); } - if offset + size as u32 > 64 { - return Err(DataPackingError::NotEnoughBits(size, offset)); + if bit_offset >= 64 { + return Err(DataPackingError::SizeExceedsMaxSize(bit_size as u8, 32)); } - let mask = (1 << size) - 1; - let extracted_bits = ((data >> offset) & mask) as i32; - // Sign extend if the extracted bits represent a negative number - let sign_bit = 1 << (size - 1); - if extracted_bits & sign_bit != 0 { - Ok(extracted_bits | !mask as i32) - } else { - Ok(extracted_bits) + if bit_offset + bit_size as u32 > 64 { + return Err(DataPackingError::NotEnoughBits(bit_size as u8, bit_offset)); } + + // Create a mask for the n-bit value + let mask = (1u64 << bit_size) - 1; + + // Extract the value from the word + let value = ((*word as u64) >> bit_offset) & mask; + + // Cast to i32 and return + Ok(value as i32) } /// Writes a specified number of bits to a given offset in a 64-bit signed integer. diff --git a/src/lib/world/src/chunk_format.rs b/src/lib/world/src/chunk_format.rs index 612a6fe7..6d09bb23 100644 --- a/src/lib/world/src/chunk_format.rs +++ b/src/lib/world/src/chunk_format.rs @@ -3,13 +3,14 @@ use crate::vanilla_chunk_format; use crate::vanilla_chunk_format::VanillaChunk; use bitcode_derive::{Decode, Encode}; use deepsize::DeepSizeOf; +use ferrumc_general_purpose::data_packing::i32::{read_nbit_i32, write_nbit_i32}; use ferrumc_macros::{NBTDeserialize, NBTSerialize}; use ferrumc_net_codec::net_types::var_int::VarInt; use lazy_static::lazy_static; use std::cmp::max; use std::collections::HashMap; use std::io::Read; -use tracing::error; +use tracing::{debug, error}; use vanilla_chunk_format::BlockData; #[cfg(test)] @@ -131,11 +132,7 @@ impl VanillaChunk { for chunk in &block_data { let mut i = 0; while i + bits_per_block < 64 { - let id = ferrumc_general_purpose::data_packing::i32::read_nbit_i32( - chunk, - bits_per_block, - i as u32, - )?; + let id = read_nbit_i32(chunk, bits_per_block as usize, i as u32)?; *block_counts.entry(id).or_insert(0) += 1; i += bits_per_block; } @@ -206,3 +203,95 @@ impl VanillaChunk { }) } } + +impl BlockStates { + pub fn resize(&mut self, new_bit_size: usize) -> Result<(), WorldError> { + let max_int_value = (1 << new_bit_size) - 1; + + if self.data.is_empty() { + let data_size = (4096 * new_bit_size + 63) / 64; + self.data = vec![0; data_size]; + self.bits_per_block = new_bit_size as u8; + return Ok(()); + } + + // Step 1: Read existing packed data into a list of normal integers + let mut normalised_ints = Vec::with_capacity(4096); + let mut values_read = 0; + + for &long in &self.data { + let mut bit_offset = 0; + + while bit_offset + self.bits_per_block as usize <= 64 { + if values_read >= 4096 { + break; + } + + // Extract value at the current bit offset + let value = read_nbit_i32(&long, self.bits_per_block as usize, bit_offset as u32)?; + if value > max_int_value { + return Err(WorldError::InvalidBlockStateData(format!( + "Value {} exceeds maximum value for {}-bit block state", + value, new_bit_size + ))); + } + normalised_ints.push(value); + values_read += 1; + + bit_offset += self.bits_per_block as usize; + } + + // Stop reading if we’ve already hit 4096 values + if values_read >= 4096 { + break; + } + } + + // Check if we read exactly 4096 block states + if normalised_ints.len() != 4096 { + return Err(WorldError::InvalidBlockStateData(format!( + "Expected 4096 block states, but got {}", + normalised_ints.len() + ))); + } + + // Step 2: Write the normalised integers into the new packed format + let mut new_data = Vec::new(); + let mut current_long: i64 = 0; + let mut bit_position = 0; + + for &value in &normalised_ints { + current_long |= (value as i64) << bit_position; + bit_position += new_bit_size; + + if bit_position >= 64 { + new_data.push(current_long); + current_long = (value as i64) >> (new_bit_size - (bit_position - 64)); + bit_position -= 64; + } + } + + // Push any remaining bits in the final long + if bit_position > 0 { + new_data.push(current_long); + } + + // Verify the size of the new data matches expectations + let expected_size = (4096 * new_bit_size + 63) / 64; + if new_data.len() != expected_size { + return Err(WorldError::InvalidBlockStateData(format!( + "Expected packed data size of {}, but got {}", + expected_size, + new_data.len() + ))); + } + + // Update the chunk with the new packed data and bit size + self.data = new_data; + self.bits_per_block = new_bit_size as u8; + + // debug!("Resize complete. New data: {:?}", self.data); + + Ok(()) + } +} diff --git a/src/lib/world/src/edits.rs b/src/lib/world/src/edits.rs index 5bdd0303..2af46ea7 100644 --- a/src/lib/world/src/edits.rs +++ b/src/lib/world/src/edits.rs @@ -51,7 +51,10 @@ impl World { let i64_index = index / blocks_per_i64; let packed_u64 = data .get(i64_index) - .ok_or(WorldError::InvalidBlockStateData())?; + .ok_or(WorldError::InvalidBlockStateData(format!( + "Invalid block state data at index {}", + i64_index + )))?; let offset = (index % blocks_per_i64) * bits_per_block; let id = ferrumc_general_purpose::data_packing::u32::read_nbit_u32( packed_u64, diff --git a/src/lib/world/src/errors.rs b/src/lib/world/src/errors.rs index ad1a1380..b4a18eda 100644 --- a/src/lib/world/src/errors.rs +++ b/src/lib/world/src/errors.rs @@ -44,7 +44,7 @@ pub enum WorldError { #[error("Section out of bounds: {0}")] SectionOutOfBounds(i32), #[error("Invalid block state data")] - InvalidBlockStateData(), + InvalidBlockStateData(String), } // implemente AcquireError for WorldError @@ -78,7 +78,7 @@ impl From for WorldError { } impl From for WorldError { - fn from(_: DataPackingError) -> Self { - WorldError::InvalidBlockStateData() + fn from(e: DataPackingError) -> Self { + WorldError::InvalidBlockStateData(e.to_string()) } } From 7acc227db743893be7a12e31aa2c473699efe0a5 Mon Sep 17 00:00:00 2001 From: ReCore Date: Tue, 31 Dec 2024 17:06:49 +1030 Subject: [PATCH 07/21] Clippy + fmt --- .../transform/update_player_position.rs | 2 +- src/bin/src/systems/chunk_sender.rs | 3 +-- .../crates/codec/src/net_types/network_position.rs | 12 +++++++++--- src/lib/net/src/packets/incoming/place_block.rs | 2 +- src/lib/net/src/utils/broadcast.rs | 1 - .../utils/general_purpose/src/data_packing/i32.rs | 2 +- src/lib/world/src/chunk_format.rs | 7 ++----- 7 files changed, 15 insertions(+), 14 deletions(-) diff --git a/src/bin/src/packet_handlers/transform/update_player_position.rs b/src/bin/src/packet_handlers/transform/update_player_position.rs index cb937152..e262e267 100644 --- a/src/bin/src/packet_handlers/transform/update_player_position.rs +++ b/src/bin/src/packet_handlers/transform/update_player_position.rs @@ -7,7 +7,7 @@ use ferrumc_net::errors::NetError; use ferrumc_net::packets::packet_events::TransformEvent; use ferrumc_net::utils::ecs_helpers::EntityExt; use ferrumc_state::GlobalState; -use tracing::{debug, trace}; +use tracing::trace; #[event_handler] async fn handle_player_move( diff --git a/src/bin/src/systems/chunk_sender.rs b/src/bin/src/systems/chunk_sender.rs index ced644ab..be723b03 100644 --- a/src/bin/src/systems/chunk_sender.rs +++ b/src/bin/src/systems/chunk_sender.rs @@ -10,12 +10,11 @@ use ferrumc_net::packets::outgoing::set_center_chunk::SetCenterChunk; use ferrumc_net_codec::encode::NetEncodeOpts; use ferrumc_net_codec::net_types::var_int::VarInt; use ferrumc_state::GlobalState; -use rand::random; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; use std::time::Duration; use tokio::task::JoinSet; -use tracing::{debug, error, info, trace}; +use tracing::{error, info, trace}; pub(super) struct ChunkSenderSystem { pub stop: AtomicBool, diff --git a/src/lib/net/crates/codec/src/net_types/network_position.rs b/src/lib/net/crates/codec/src/net_types/network_position.rs index cc428ab3..fc787a79 100644 --- a/src/lib/net/crates/codec/src/net_types/network_position.rs +++ b/src/lib/net/crates/codec/src/net_types/network_position.rs @@ -68,9 +68,15 @@ impl NetworkPosition { let mut x = (val >> 38) as i32; let mut y = (val << 52 >> 52) as i16; let mut z = (val << 26 >> 38) as i32; - if x >= 1 << 25 { x -= 1 << 26 } - if y >= 1 << 11 { y -= 1 << 12 } - if z >= 1 << 25 { z -= 1 << 26 } + if x >= 1 << 25 { + x -= 1 << 26 + } + if y >= 1 << 11 { + y -= 1 << 12 + } + if z >= 1 << 25 { + z -= 1 << 26 + } Self { x, y, z } } } diff --git a/src/lib/net/src/packets/incoming/place_block.rs b/src/lib/net/src/packets/incoming/place_block.rs index 0b891a29..4d0bda06 100644 --- a/src/lib/net/src/packets/incoming/place_block.rs +++ b/src/lib/net/src/packets/incoming/place_block.rs @@ -21,7 +21,7 @@ pub struct PlaceBlock { } impl IncomingPacket for PlaceBlock { - async fn handle(self, conn_id: usize, state: Arc) -> NetResult<()> { + async fn handle(self, _conn_id: usize, _state: Arc) -> NetResult<()> { debug!("{:?}", self); Ok(()) } diff --git a/src/lib/net/src/utils/broadcast.rs b/src/lib/net/src/utils/broadcast.rs index b2fa6468..51a9dc99 100644 --- a/src/lib/net/src/utils/broadcast.rs +++ b/src/lib/net/src/utils/broadcast.rs @@ -1,7 +1,6 @@ use crate::connection::StreamWriter; use crate::NetResult; use async_trait::async_trait; -use ferrumc_core::chunks::chunk_receiver::ChunkReceiver; use ferrumc_core::identity::player_identity::PlayerIdentity; use ferrumc_ecs::entities::Entity; use ferrumc_net_codec::encode::{NetEncode, NetEncodeOpts}; diff --git a/src/lib/utils/general_purpose/src/data_packing/i32.rs b/src/lib/utils/general_purpose/src/data_packing/i32.rs index d79b10f7..4d4f20db 100644 --- a/src/lib/utils/general_purpose/src/data_packing/i32.rs +++ b/src/lib/utils/general_purpose/src/data_packing/i32.rs @@ -17,7 +17,7 @@ use crate::data_packing::errors::DataPackingError; /// /// * `DataPackingError::SizeExceedsMaxSize` - If `size` is greater than 32. /// * `DataPackingError::NotEnoughBits` - If `offset + size` exceeds 64 bits. -/// Reads an n-bit integer from a packed `i64`. +/// Reads an n-bit integer from a packed `i64`. pub fn read_nbit_i32( word: &i64, bit_size: usize, diff --git a/src/lib/world/src/chunk_format.rs b/src/lib/world/src/chunk_format.rs index 6d09bb23..88925abd 100644 --- a/src/lib/world/src/chunk_format.rs +++ b/src/lib/world/src/chunk_format.rs @@ -3,14 +3,14 @@ use crate::vanilla_chunk_format; use crate::vanilla_chunk_format::VanillaChunk; use bitcode_derive::{Decode, Encode}; use deepsize::DeepSizeOf; -use ferrumc_general_purpose::data_packing::i32::{read_nbit_i32, write_nbit_i32}; +use ferrumc_general_purpose::data_packing::i32::read_nbit_i32; use ferrumc_macros::{NBTDeserialize, NBTSerialize}; use ferrumc_net_codec::net_types::var_int::VarInt; use lazy_static::lazy_static; use std::cmp::max; use std::collections::HashMap; use std::io::Read; -use tracing::{debug, error}; +use tracing::error; use vanilla_chunk_format::BlockData; #[cfg(test)] @@ -285,13 +285,10 @@ impl BlockStates { new_data.len() ))); } - // Update the chunk with the new packed data and bit size self.data = new_data; self.bits_per_block = new_bit_size as u8; - // debug!("Resize complete. New data: {:?}", self.data); - Ok(()) } } From aaf5d34f011f1f776803e9b0b7b5828101014319 Mon Sep 17 00:00:00 2001 From: ReCore Date: Tue, 31 Dec 2024 17:15:06 +1030 Subject: [PATCH 08/21] Fix tests --- src/lib/utils/general_purpose/src/data_packing/i32.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/utils/general_purpose/src/data_packing/i32.rs b/src/lib/utils/general_purpose/src/data_packing/i32.rs index 4d4f20db..aecbde08 100644 --- a/src/lib/utils/general_purpose/src/data_packing/i32.rs +++ b/src/lib/utils/general_purpose/src/data_packing/i32.rs @@ -88,8 +88,8 @@ mod tests { fn test_read_nbit_i32() { let data: i64 = 0b110101011; assert_eq!(read_nbit_i32(&data, 3, 0).unwrap(), 0b011); - assert_eq!(read_nbit_i32(&data, 3, 3).unwrap(), -3); // 0b101 as i32 is -3 - assert_eq!(read_nbit_i32(&data, 3, 6).unwrap(), -2); // 0b110 as i32 is -2 + assert_eq!(read_nbit_i32(&data, 3, 3).unwrap(), 0b101); + assert_eq!(read_nbit_i32(&data, 3, 6).unwrap(), 0b110); assert_eq!(read_nbit_i32(&data, 3, 9).unwrap(), 0b000); } From a4e8ad9427ff83c416297d9f2a4b313e34fedd79 Mon Sep 17 00:00:00 2001 From: ReCore Date: Tue, 21 Jan 2025 14:54:44 +1030 Subject: [PATCH 09/21] Now prints blocks clicked Also teleports you way up in the sky, no more spawning in the ground --- scripts/new_packet.py | 7 +-- src/bin/src/systems/chunk_sender.rs | 8 ++-- src/lib/core/src/chunks/chunk_receiver.rs | 3 ++ src/lib/derive_macros/src/net/packets/mod.rs | 2 +- src/lib/net/src/errors.rs | 7 ++- .../src/packets/incoming/chunk_batch_ack.rs | 45 +++++++++++++++++++ .../src/packets/incoming/chunks_per_tick.rs | 20 --------- src/lib/net/src/packets/incoming/mod.rs | 8 ++-- .../net/src/packets/incoming/place_block.rs | 15 +++++-- .../outgoing/set_default_spawn_position.rs | 2 +- 10 files changed, 79 insertions(+), 38 deletions(-) create mode 100644 src/lib/net/src/packets/incoming/chunk_batch_ack.rs delete mode 100644 src/lib/net/src/packets/incoming/chunks_per_tick.rs diff --git a/scripts/new_packet.py b/scripts/new_packet.py index 0f493608..9db5e790 100644 --- a/scripts/new_packet.py +++ b/scripts/new_packet.py @@ -8,7 +8,7 @@ use std::sync::Arc; #[derive(NetDecode)] -#[packet(packet_id = ++id++, state = "play")] +#[packet(packet_id = "++id++", state = "play")] pub struct ++name++ { } @@ -24,7 +24,7 @@ use std::io::Write; #[derive(NetEncode)] -#[packet(packet_id = ++id++)] +#[packet(packet_id = "++id++")] pub struct ++name++ {} """ @@ -50,7 +50,8 @@ def to_camel_case(string) -> str: packet_name = input("Packet name: ") packets_dir = os.path.join(os.path.join(os.path.dirname(__file__), ".."), "src/lib/net/src/packets") -packet_id = input("Packet ID (formatted like 0x01): ") +packet_id = input( + "Packet ID (formatted as snake case, look on https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/Protocol if you need to get the id): ") packet_id = packet_id[:-2] + packet_id[-2:].upper() with open(f"{packets_dir}/{packet_type}/{to_snake_case(packet_name)}.rs", "x") as f: diff --git a/src/bin/src/systems/chunk_sender.rs b/src/bin/src/systems/chunk_sender.rs index 9053ecd6..ffaed9d0 100644 --- a/src/bin/src/systems/chunk_sender.rs +++ b/src/bin/src/systems/chunk_sender.rs @@ -69,13 +69,13 @@ impl System for ChunkSenderSystem { } { trace!("Getting chunk_recv 3 for sender"); - let chunk_recv = state + let mut chunk_recv = state .universe .get_mut::(eid) .expect("ChunkReceiver not found"); trace!("Got chunk_recv 3 for sender"); - for mut possible_chunk in chunk_recv.needed_chunks.iter_mut() { - if let (key, Some(chunk)) = possible_chunk.pair_mut() { + for (key, chunk) in chunk_recv.needed_chunks.iter_mut() { + if let Some(chunk) = chunk { chunk.sections.iter_mut().for_each(|section| { // if random::() < 25 { if let Err(e) = section.block_states.resize(8) { @@ -138,7 +138,7 @@ impl System for ChunkSenderSystem { } if let Err(e) = conn.send_packet( ChunkBatchFinish { - batch_size: VarInt::new(sent_chunks), + batch_size: VarInt::new(count), }, &NetEncodeOpts::WithLength, ) { diff --git a/src/lib/core/src/chunks/chunk_receiver.rs b/src/lib/core/src/chunks/chunk_receiver.rs index 37d71b5f..3deb15ac 100644 --- a/src/lib/core/src/chunks/chunk_receiver.rs +++ b/src/lib/core/src/chunks/chunk_receiver.rs @@ -1,5 +1,6 @@ use ferrumc_world::chunk_format::Chunk; use std::collections::{HashMap, HashSet}; +use std::sync::atomic::AtomicBool; use tokio::time::Instant; const VIEW_DISTANCE: i32 = 8; @@ -9,6 +10,7 @@ pub struct ChunkReceiver { pub last_update: Instant, pub last_chunk: Option<(i32, i32, String)>, pub chunks_per_tick: f32, + pub has_loaded: AtomicBool, } impl Default for ChunkReceiver { @@ -25,6 +27,7 @@ impl ChunkReceiver { last_update: Instant::now(), last_chunk: None, chunks_per_tick: 0.0, + has_loaded: AtomicBool::new(false), } } } diff --git a/src/lib/derive_macros/src/net/packets/mod.rs b/src/lib/derive_macros/src/net/packets/mod.rs index 0bec0cff..44fbd9c8 100644 --- a/src/lib/derive_macros/src/net/packets/mod.rs +++ b/src/lib/derive_macros/src/net/packets/mod.rs @@ -118,7 +118,7 @@ pub fn bake_registry(input: TokenStream) -> TokenStream { .expect( "parse_packet_attribute failed\ \nPlease provide the packet_id and state fields in the #[packet(...)] attribute.\ - \nExample: #[packet(packet_id = 0x00, state = \"handshake\")]", + \nExample: #[packet(packet_id = \"example_packet\", state = \"handshake\")]", ); let struct_name = &item_struct.ident; diff --git a/src/lib/net/src/errors.rs b/src/lib/net/src/errors.rs index d24d3684..e61863a0 100644 --- a/src/lib/net/src/errors.rs +++ b/src/lib/net/src/errors.rs @@ -38,11 +38,14 @@ pub enum NetError { #[error("Invalid State: {0}")] InvalidState(u8), - #[error("{0}")] + #[error("Packet error: {0}")] Packet(#[from] PacketError), - #[error("{0}")] + #[error("Chunk error: {0}")] Chunk(#[from] ChunkError), + + #[error("World error: {0}")] + World(#[from] ferrumc_world::errors::WorldError), } #[derive(Debug, Error)] diff --git a/src/lib/net/src/packets/incoming/chunk_batch_ack.rs b/src/lib/net/src/packets/incoming/chunk_batch_ack.rs new file mode 100644 index 00000000..83635945 --- /dev/null +++ b/src/lib/net/src/packets/incoming/chunk_batch_ack.rs @@ -0,0 +1,45 @@ +use crate::connection::StreamWriter; +use crate::packets::outgoing::synchronize_player_position::SynchronizePlayerPositionPacket; +use crate::packets::IncomingPacket; +use crate::NetResult; +use ferrumc_core::chunks::chunk_receiver::ChunkReceiver; +use ferrumc_macros::{packet, NetDecode}; +use ferrumc_net_codec::encode::NetEncodeOpts; +use ferrumc_state::ServerState; +use std::sync::Arc; + +#[derive(NetDecode)] +#[packet(packet_id = "chunk_batch_received", state = "play")] +pub struct ChunkBatchAck { + chunks_per_tick: f32, +} + +impl IncomingPacket for ChunkBatchAck { + async fn handle(self, conn_id: usize, state: Arc) -> NetResult<()> { + // The first chunk batch should be the ones sent when the player first joins the server. + // This just moves them to their spawn position when all their chunks are done loading, + // preventing them from falling into the floor. + let mut move_to_spawn = false; + { + let mut chunk_recv = state.universe.get_mut::(conn_id)?; + chunk_recv.chunks_per_tick = self.chunks_per_tick; + if !chunk_recv + .has_loaded + .load(std::sync::atomic::Ordering::Relaxed) + { + move_to_spawn = true; + chunk_recv + .has_loaded + .store(true, std::sync::atomic::Ordering::Relaxed); + } + } + if move_to_spawn { + let mut conn = state.universe.get_mut::(conn_id)?; + conn.send_packet( + SynchronizePlayerPositionPacket::default(), + &NetEncodeOpts::WithLength, + )?; + } + Ok(()) + } +} diff --git a/src/lib/net/src/packets/incoming/chunks_per_tick.rs b/src/lib/net/src/packets/incoming/chunks_per_tick.rs deleted file mode 100644 index a0f1f759..00000000 --- a/src/lib/net/src/packets/incoming/chunks_per_tick.rs +++ /dev/null @@ -1,20 +0,0 @@ -use crate::packets::IncomingPacket; -use crate::NetResult; -use ferrumc_core::chunks::chunk_receiver::ChunkReceiver; -use ferrumc_macros::{packet, NetDecode}; -use ferrumc_state::ServerState; -use std::sync::Arc; - -#[derive(NetDecode)] -#[packet(packet_id = 0x08, state = "play")] -pub struct ChunksPerTick { - chunks_per_tick: f32, -} - -impl IncomingPacket for ChunksPerTick { - async fn handle(self, conn_id: usize, state: Arc) -> NetResult<()> { - let mut chunk_recv = state.universe.get_mut::(conn_id)?; - chunk_recv.chunks_per_tick = self.chunks_per_tick; - Ok(()) - } -} diff --git a/src/lib/net/src/packets/incoming/mod.rs b/src/lib/net/src/packets/incoming/mod.rs index 7f233987..6701becf 100644 --- a/src/lib/net/src/packets/incoming/mod.rs +++ b/src/lib/net/src/packets/incoming/mod.rs @@ -11,11 +11,11 @@ pub mod status_request; pub mod keep_alive; pub mod packet_skeleton; +pub mod place_block; +pub mod player_command; pub mod set_player_position; pub mod set_player_position_and_rotation; pub mod set_player_rotation; - -pub mod chunks_per_tick; -pub mod place_block; -pub mod player_command; pub mod swing_arm; + +pub mod chunk_batch_ack; diff --git a/src/lib/net/src/packets/incoming/place_block.rs b/src/lib/net/src/packets/incoming/place_block.rs index 4d0bda06..8a7906f2 100644 --- a/src/lib/net/src/packets/incoming/place_block.rs +++ b/src/lib/net/src/packets/incoming/place_block.rs @@ -8,7 +8,7 @@ use std::sync::Arc; use tracing::debug; #[derive(NetDecode, Debug)] -#[packet(packet_id = 0x38, state = "play")] +#[packet(packet_id = "use_item_on", state = "play")] pub struct PlaceBlock { pub hand: VarInt, pub position: NetworkPosition, @@ -21,8 +21,17 @@ pub struct PlaceBlock { } impl IncomingPacket for PlaceBlock { - async fn handle(self, _conn_id: usize, _state: Arc) -> NetResult<()> { - debug!("{:?}", self); + async fn handle(self, _conn_id: usize, state: Arc) -> NetResult<()> { + let block_clicked = state + .world + .get_block( + self.position.x, + self.position.y as i32, + self.position.z, + "overworld", + ) + .await?; + debug!("Block clicked: {:?}", block_clicked); Ok(()) } } diff --git a/src/lib/net/src/packets/outgoing/set_default_spawn_position.rs b/src/lib/net/src/packets/outgoing/set_default_spawn_position.rs index d8cff7b8..85f70543 100644 --- a/src/lib/net/src/packets/outgoing/set_default_spawn_position.rs +++ b/src/lib/net/src/packets/outgoing/set_default_spawn_position.rs @@ -9,7 +9,7 @@ pub struct SetDefaultSpawnPositionPacket { pub angle: f32, } -pub const DEFAULT_SPAWN_POSITION: NetworkPosition = NetworkPosition { x: 0, y: 256, z: 0 }; +pub const DEFAULT_SPAWN_POSITION: NetworkPosition = NetworkPosition { x: 0, y: 320, z: 0 }; const DEFAULT_ANGLE: f32 = 0.0; From 75a2f04fa6c1e581300e1350f4ea303aa0c932f6 Mon Sep 17 00:00:00 2001 From: ReCore Date: Tue, 21 Jan 2025 17:48:02 +1030 Subject: [PATCH 10/21] Now sets blocks correctly --- src/lib/core/src/chunks/chunk_receiver.rs | 4 + .../src/packets/incoming/chunk_batch_ack.rs | 13 +++ .../net/src/packets/incoming/place_block.rs | 24 +++++ .../outgoing/set_default_spawn_position.rs | 2 +- .../packets/outgoing/set_render_distance.rs | 2 +- .../general_purpose/src/data_packing/u32.rs | 16 +-- src/lib/utils/logging/src/lib.rs | 4 + src/lib/world/src/chunk_format.rs | 23 ++-- src/lib/world/src/edits.rs | 100 +++++++++++++++++- src/lib/world/src/errors.rs | 2 + src/lib/world/src/lib.rs | 2 +- 11 files changed, 171 insertions(+), 21 deletions(-) diff --git a/src/lib/core/src/chunks/chunk_receiver.rs b/src/lib/core/src/chunks/chunk_receiver.rs index 3deb15ac..07c38dce 100644 --- a/src/lib/core/src/chunks/chunk_receiver.rs +++ b/src/lib/core/src/chunks/chunk_receiver.rs @@ -30,6 +30,10 @@ impl ChunkReceiver { has_loaded: AtomicBool::new(false), } } + + pub fn queue_chunk_resend(&mut self, x: i32, z: i32, dimension: String) { + self.needed_chunks.insert((x, z, dimension), None); + } } impl ChunkReceiver { diff --git a/src/lib/net/src/packets/incoming/chunk_batch_ack.rs b/src/lib/net/src/packets/incoming/chunk_batch_ack.rs index 83635945..6d7f36d1 100644 --- a/src/lib/net/src/packets/incoming/chunk_batch_ack.rs +++ b/src/lib/net/src/packets/incoming/chunk_batch_ack.rs @@ -7,6 +7,7 @@ use ferrumc_macros::{packet, NetDecode}; use ferrumc_net_codec::encode::NetEncodeOpts; use ferrumc_state::ServerState; use std::sync::Arc; +use ferrumc_core::transform::position::Position; #[derive(NetDecode)] #[packet(packet_id = "chunk_batch_received", state = "play")] @@ -33,6 +34,18 @@ impl IncomingPacket for ChunkBatchAck { .store(true, std::sync::atomic::Ordering::Relaxed); } } + { + // If they aren't underground, don't move them to spawn + let pos = state.universe.get_mut::(conn_id)?; + let head_block = state + .world + .get_block(pos.x as i32, pos.y as i32 - 1, pos.z as i32, "overworld") + .await?; + if head_block.name == "minecraft:air" { + move_to_spawn = false; + } + + } if move_to_spawn { let mut conn = state.universe.get_mut::(conn_id)?; conn.send_packet( diff --git a/src/lib/net/src/packets/incoming/place_block.rs b/src/lib/net/src/packets/incoming/place_block.rs index 8a7906f2..e4128182 100644 --- a/src/lib/net/src/packets/incoming/place_block.rs +++ b/src/lib/net/src/packets/incoming/place_block.rs @@ -1,9 +1,11 @@ use crate::packets::IncomingPacket; use crate::NetResult; +use ferrumc_core::chunks::chunk_receiver::ChunkReceiver; use ferrumc_macros::{packet, NetDecode}; use ferrumc_net_codec::net_types::network_position::NetworkPosition; use ferrumc_net_codec::net_types::var_int::VarInt; use ferrumc_state::ServerState; +use ferrumc_world::vanilla_chunk_format::BlockData; use std::sync::Arc; use tracing::debug; @@ -23,6 +25,7 @@ pub struct PlaceBlock { impl IncomingPacket for PlaceBlock { async fn handle(self, _conn_id: usize, state: Arc) -> NetResult<()> { let block_clicked = state + .clone() .world .get_block( self.position.x, @@ -32,6 +35,27 @@ impl IncomingPacket for PlaceBlock { ) .await?; debug!("Block clicked: {:?}", block_clicked); + state + .world + .set_block( + self.position.x, + self.position.y as i32, + self.position.z, + "overworld", + BlockData { + name: "minecraft:stone".to_string(), + properties: None, + }, + ) + .await?; + let q = state.universe.query::<&mut ChunkReceiver>(); + for (_, mut chunk_recv) in q { + chunk_recv.queue_chunk_resend( + self.position.x >> 4, + self.position.z >> 4, + "overworld".to_string(), + ); + } Ok(()) } } diff --git a/src/lib/net/src/packets/outgoing/set_default_spawn_position.rs b/src/lib/net/src/packets/outgoing/set_default_spawn_position.rs index 85f70543..d8cff7b8 100644 --- a/src/lib/net/src/packets/outgoing/set_default_spawn_position.rs +++ b/src/lib/net/src/packets/outgoing/set_default_spawn_position.rs @@ -9,7 +9,7 @@ pub struct SetDefaultSpawnPositionPacket { pub angle: f32, } -pub const DEFAULT_SPAWN_POSITION: NetworkPosition = NetworkPosition { x: 0, y: 320, z: 0 }; +pub const DEFAULT_SPAWN_POSITION: NetworkPosition = NetworkPosition { x: 0, y: 256, z: 0 }; const DEFAULT_ANGLE: f32 = 0.0; diff --git a/src/lib/net/src/packets/outgoing/set_render_distance.rs b/src/lib/net/src/packets/outgoing/set_render_distance.rs index 6c038c10..6257b741 100644 --- a/src/lib/net/src/packets/outgoing/set_render_distance.rs +++ b/src/lib/net/src/packets/outgoing/set_render_distance.rs @@ -8,7 +8,7 @@ pub struct SetRenderDistance { pub distance: VarInt, } -const DEFAULT_RENDER_DISTANCE: u8 = 5; +const DEFAULT_RENDER_DISTANCE: u8 = 18; impl Default for SetRenderDistance { fn default() -> Self { diff --git a/src/lib/utils/general_purpose/src/data_packing/u32.rs b/src/lib/utils/general_purpose/src/data_packing/u32.rs index 91e2ed48..7e79db29 100644 --- a/src/lib/utils/general_purpose/src/data_packing/u32.rs +++ b/src/lib/utils/general_purpose/src/data_packing/u32.rs @@ -46,23 +46,27 @@ pub fn read_nbit_u32(data: &i64, size: u8, offset: u32) -> Result Result<(), DataPackingError> { + if size == 0 { + return Ok(()); // Nothing to do for 0 bits + } if size > 32 { return Err(DataPackingError::SizeExceedsMaxSize(size, 32)); } - if offset + size as u32 > 64 { + if offset >= 64 || offset + size as u32 > 64 { return Err(DataPackingError::NotEnoughBits(size, offset)); } - let mask = (1 << size) - 1; - *data &= !((mask) << offset); - *data |= ((value as u64) & mask) << offset; + let mask = ((1u64 << size) - 1) as i64; // Use u64 to avoid overflow + *data &= !(mask << offset); // Clear the target bits + *data |= ((value as i64) & mask) << offset; // Write the new value Ok(()) } + #[cfg(test)] mod tests { use super::*; @@ -80,7 +84,7 @@ mod tests { /// Tests the `write_nbit_u32` function with various inputs. #[test] fn test_write_nbit_u32() { - let mut data: u64 = 0; + let mut data: i64 = 0; write_nbit_u32(&mut data, 0, 0b011, 3).unwrap(); assert_eq!(data, 0b011); write_nbit_u32(&mut data, 3, 0b101, 3).unwrap(); diff --git a/src/lib/utils/logging/src/lib.rs b/src/lib/utils/logging/src/lib.rs index 97fece40..fe9ebbed 100644 --- a/src/lib/utils/logging/src/lib.rs +++ b/src/lib/utils/logging/src/lib.rs @@ -45,6 +45,10 @@ pub fn init_logging(trace_level: Level) { .with_thread_ids(false) .with_thread_names(false); } + #[cfg(debug_assertions)] + { + fmt_layer = fmt_layer.with_file(true).with_line_number(true); + } let profiler_layer = ProfilerTracingLayer; diff --git a/src/lib/world/src/chunk_format.rs b/src/lib/world/src/chunk_format.rs index 88925abd..54fab67b 100644 --- a/src/lib/world/src/chunk_format.rs +++ b/src/lib/world/src/chunk_format.rs @@ -72,7 +72,7 @@ pub struct BlockStates { pub non_air_blocks: u16, pub data: Vec, pub palette: Vec, - pub block_counts: HashMap, + pub block_counts: HashMap, } fn convert_to_net_palette(vanilla_palettes: Vec) -> Result, WorldError> { @@ -132,24 +132,25 @@ impl VanillaChunk { for chunk in &block_data { let mut i = 0; while i + bits_per_block < 64 { - let id = read_nbit_i32(chunk, bits_per_block as usize, i as u32)?; - *block_counts.entry(id).or_insert(0) += 1; + let palette_index = read_nbit_i32(chunk, bits_per_block as usize, i as u32)?; + let block = palette + .get(palette_index as usize) + .unwrap_or(&BlockData::default()) + .clone(); + *block_counts.entry(block).or_insert(0) += 1; i += bits_per_block; } } if block_data.is_empty() { let single_block = if let Some(block) = palette.first() { - if let Some(id) = BLOCK2ID.get(block) { - *id - } else { - 0 - } + block } else { - 0 + &BlockData::default() }; - block_counts.insert(single_block, 4096); + block_counts.insert(single_block.clone(), 4096); } - let non_air_blocks = 4096 - *block_counts.get(&0).unwrap_or(&0) as u16; + let non_air_blocks = + 4096 - *block_counts.get(&BlockData::default()).unwrap_or(&0) as u16; let block_states = BlockStates { bits_per_block, block_counts, diff --git a/src/lib/world/src/edits.rs b/src/lib/world/src/edits.rs index 2af46ea7..e5a08cf8 100644 --- a/src/lib/world/src/edits.rs +++ b/src/lib/world/src/edits.rs @@ -1,7 +1,9 @@ -use crate::chunk_format::ID2BLOCK; +use crate::chunk_format::{BLOCK2ID, ID2BLOCK}; use crate::errors::WorldError; +use crate::errors::WorldError::InvalidBlockStateData; use crate::vanilla_chunk_format::BlockData; use crate::World; +use std::cmp::max; impl World { /// Asynchronously retrieves the block data at the specified coordinates in the given dimension. @@ -71,4 +73,100 @@ impl World { .unwrap_or(&BlockData::default()) .clone()) } + + pub async fn set_block( + &self, + x: i32, + y: i32, + z: i32, + dimension: &str, + block: BlockData, + ) -> Result<(), WorldError> { + if !BLOCK2ID.contains_key(&block) { + return Err(WorldError::InvalidBlock(block)); + }; + // Get chunk + let chunk_x = x >> 4; + let chunk_z = z >> 4; + let mut chunk = self.load_chunk(chunk_x, chunk_z, dimension).await?; + let section = chunk + .sections + .iter_mut() + .find(|section| section.y == (y >> 4) as i8) + .ok_or(WorldError::SectionOutOfBounds(y >> 4))?; + let mut bits_per_block = section.block_states.bits_per_block; + // Remove old block + let old_block = self.get_block(x, y, z, dimension).await?; + let old_block_count = section + .block_states + .block_counts + .get_mut(&old_block) + .expect("Block not found"); + *old_block_count -= 1; + if *old_block_count == 0 { + section.block_states.block_counts.remove(&old_block); + } + let block_id = BLOCK2ID + .get(&block) + .ok_or(WorldError::InvalidBlock(block.clone()))?; + // Add new block + if let Some(e) = section.block_states.block_counts.get(&block) { + section.block_states.block_counts.insert(block, e + 1); + } else { + section.block_states.block_counts.insert(block, 1); + } + // Check if we need to resize bits_per_block + let new_bits_per_block = max( + (section.block_states.block_counts.len() as f32) + .log2() + .ceil() as u8, + 4, + ); + if new_bits_per_block != bits_per_block { + section.block_states.resize(new_bits_per_block as usize)?; + bits_per_block = new_bits_per_block; + } + // Get block index + let mut block_palette_index = -1i16; + for (index, palette) in section.block_states.palette.iter().enumerate() { + if palette.val == *block_id { + block_palette_index = index as i16; + break; + } + } + // Add block to palette if it doesn't exist + if block_palette_index == -1 { + block_palette_index = section.block_states.palette.len() as i16; + section.block_states.palette.push((*block_id).into()); + } + // Set block + let blocks_per_i64 = (64f64 / bits_per_block as f64).floor() as usize; + let index = ((y & 0xf) * 256 + (z & 0xf) * 16 + (x & 0xf)) as usize; + let i64_index = index / blocks_per_i64; + let packed_u64 = + section + .block_states + .data + .get_mut(i64_index) + .ok_or(InvalidBlockStateData(format!( + "Invalid block state data at index {}", + i64_index + )))?; + let offset = (index % blocks_per_i64) * bits_per_block as usize; + if let Err(e) = ferrumc_general_purpose::data_packing::u32::write_nbit_u32( + packed_u64, + offset as u32, + block_palette_index as u32, + bits_per_block, + ) { + return Err(InvalidBlockStateData(format!( + "Failed to write block: {}", + e + ))); + } + // Save chunk + self.save_chunk(chunk).await?; + // TODO: Remove empty palette entries + Ok(()) + } } diff --git a/src/lib/world/src/errors.rs b/src/lib/world/src/errors.rs index b4a18eda..998c8bcf 100644 --- a/src/lib/world/src/errors.rs +++ b/src/lib/world/src/errors.rs @@ -45,6 +45,8 @@ pub enum WorldError { SectionOutOfBounds(i32), #[error("Invalid block state data")] InvalidBlockStateData(String), + #[error("Invalid block: {0}")] + InvalidBlock(BlockData), } // implemente AcquireError for WorldError diff --git a/src/lib/world/src/lib.rs b/src/lib/world/src/lib.rs index 52a00b03..a58afdd0 100644 --- a/src/lib/world/src/lib.rs +++ b/src/lib/world/src/lib.rs @@ -5,7 +5,7 @@ mod db_functions; pub mod edits; pub mod errors; mod importing; -mod vanilla_chunk_format; +pub mod vanilla_chunk_format; use crate::chunk_format::Chunk; use crate::errors::WorldError; From bf07482c0a7849d05741114b3f049543bcecec6b Mon Sep 17 00:00:00 2001 From: ReCore Date: Tue, 21 Jan 2025 18:49:30 +1030 Subject: [PATCH 11/21] Breaking blocks and removing empty palette entries --- src/lib/core/src/chunks/chunk_receiver.rs | 6 +- .../src/packets/incoming/chunk_batch_ack.rs | 3 +- src/lib/net/src/packets/incoming/mod.rs | 2 + .../net/src/packets/incoming/player_action.rs | 45 ++++++++++ .../packets/outgoing/set_render_distance.rs | 2 +- .../general_purpose/src/data_packing/u32.rs | 1 - src/lib/world/src/edits.rs | 87 +++++++++++++------ 7 files changed, 115 insertions(+), 31 deletions(-) create mode 100644 src/lib/net/src/packets/incoming/player_action.rs diff --git a/src/lib/core/src/chunks/chunk_receiver.rs b/src/lib/core/src/chunks/chunk_receiver.rs index 07c38dce..b796620f 100644 --- a/src/lib/core/src/chunks/chunk_receiver.rs +++ b/src/lib/core/src/chunks/chunk_receiver.rs @@ -30,9 +30,11 @@ impl ChunkReceiver { has_loaded: AtomicBool::new(false), } } - + pub fn queue_chunk_resend(&mut self, x: i32, z: i32, dimension: String) { - self.needed_chunks.insert((x, z, dimension), None); + if self.can_see.contains(&(x, z, dimension.clone())) { + self.needed_chunks.insert((x, z, dimension), None); + } } } diff --git a/src/lib/net/src/packets/incoming/chunk_batch_ack.rs b/src/lib/net/src/packets/incoming/chunk_batch_ack.rs index 6d7f36d1..7d8d7695 100644 --- a/src/lib/net/src/packets/incoming/chunk_batch_ack.rs +++ b/src/lib/net/src/packets/incoming/chunk_batch_ack.rs @@ -3,11 +3,11 @@ use crate::packets::outgoing::synchronize_player_position::SynchronizePlayerPosi use crate::packets::IncomingPacket; use crate::NetResult; use ferrumc_core::chunks::chunk_receiver::ChunkReceiver; +use ferrumc_core::transform::position::Position; use ferrumc_macros::{packet, NetDecode}; use ferrumc_net_codec::encode::NetEncodeOpts; use ferrumc_state::ServerState; use std::sync::Arc; -use ferrumc_core::transform::position::Position; #[derive(NetDecode)] #[packet(packet_id = "chunk_batch_received", state = "play")] @@ -44,7 +44,6 @@ impl IncomingPacket for ChunkBatchAck { if head_block.name == "minecraft:air" { move_to_spawn = false; } - } if move_to_spawn { let mut conn = state.universe.get_mut::(conn_id)?; diff --git a/src/lib/net/src/packets/incoming/mod.rs b/src/lib/net/src/packets/incoming/mod.rs index 6701becf..3a9b8063 100644 --- a/src/lib/net/src/packets/incoming/mod.rs +++ b/src/lib/net/src/packets/incoming/mod.rs @@ -19,3 +19,5 @@ pub mod set_player_rotation; pub mod swing_arm; pub mod chunk_batch_ack; + +pub mod player_action; diff --git a/src/lib/net/src/packets/incoming/player_action.rs b/src/lib/net/src/packets/incoming/player_action.rs new file mode 100644 index 00000000..bc5962b7 --- /dev/null +++ b/src/lib/net/src/packets/incoming/player_action.rs @@ -0,0 +1,45 @@ +use crate::packets::IncomingPacket; +use crate::NetResult; +use ferrumc_macros::{packet, NetDecode}; +use ferrumc_net_codec::net_types::network_position::NetworkPosition; +use ferrumc_net_codec::net_types::var_int::VarInt; +use ferrumc_state::ServerState; +use std::sync::Arc; +use tracing::debug; + +#[derive(NetDecode)] +#[packet(packet_id = "player_action", state = "play")] +pub struct PlayerAction { + pub status: VarInt, + pub location: NetworkPosition, + pub face: u8, + pub sequence: VarInt, +} + +impl IncomingPacket for PlayerAction { + async fn handle(self, _: usize, state: Arc) -> NetResult<()> { + // https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/Protocol?oldid=2773393#Player_Action + match self.status.val { + 0 => { + state + .world + .set_block( + self.location.x, + self.location.y as i32, + self.location.z, + "overworld", + ferrumc_world::vanilla_chunk_format::BlockData { + name: "minecraft:air".to_string(), + properties: None, + }, + ) + .await?; + } + 1 => { + debug!("You shouldn't be seeing this in creative mode."); + } + _ => {} + }; + Ok(()) + } +} diff --git a/src/lib/net/src/packets/outgoing/set_render_distance.rs b/src/lib/net/src/packets/outgoing/set_render_distance.rs index 6257b741..6c038c10 100644 --- a/src/lib/net/src/packets/outgoing/set_render_distance.rs +++ b/src/lib/net/src/packets/outgoing/set_render_distance.rs @@ -8,7 +8,7 @@ pub struct SetRenderDistance { pub distance: VarInt, } -const DEFAULT_RENDER_DISTANCE: u8 = 18; +const DEFAULT_RENDER_DISTANCE: u8 = 5; impl Default for SetRenderDistance { fn default() -> Self { diff --git a/src/lib/utils/general_purpose/src/data_packing/u32.rs b/src/lib/utils/general_purpose/src/data_packing/u32.rs index 7e79db29..92175e21 100644 --- a/src/lib/utils/general_purpose/src/data_packing/u32.rs +++ b/src/lib/utils/general_purpose/src/data_packing/u32.rs @@ -66,7 +66,6 @@ pub fn write_nbit_u32( Ok(()) } - #[cfg(test)] mod tests { use super::*; diff --git a/src/lib/world/src/edits.rs b/src/lib/world/src/edits.rs index e5a08cf8..7c03f47a 100644 --- a/src/lib/world/src/edits.rs +++ b/src/lib/world/src/edits.rs @@ -4,6 +4,7 @@ use crate::errors::WorldError::InvalidBlockStateData; use crate::vanilla_chunk_format::BlockData; use crate::World; use std::cmp::max; +use tracing::debug; impl World { /// Asynchronously retrieves the block data at the specified coordinates in the given dimension. @@ -103,8 +104,10 @@ impl World { .get_mut(&old_block) .expect("Block not found"); *old_block_count -= 1; - if *old_block_count == 0 { + let mut remove_old_block = false; + if *old_block_count <= 0 { section.block_states.block_counts.remove(&old_block); + remove_old_block = true; } let block_id = BLOCK2ID .get(&block) @@ -115,30 +118,18 @@ impl World { } else { section.block_states.block_counts.insert(block, 1); } - // Check if we need to resize bits_per_block - let new_bits_per_block = max( - (section.block_states.block_counts.len() as f32) - .log2() - .ceil() as u8, - 4, - ); - if new_bits_per_block != bits_per_block { - section.block_states.resize(new_bits_per_block as usize)?; - bits_per_block = new_bits_per_block; - } // Get block index - let mut block_palette_index = -1i16; - for (index, palette) in section.block_states.palette.iter().enumerate() { - if palette.val == *block_id { - block_palette_index = index as i16; - break; - } - } - // Add block to palette if it doesn't exist - if block_palette_index == -1 { - block_palette_index = section.block_states.palette.len() as i16; - section.block_states.palette.push((*block_id).into()); - } + let block_palette_index = section + .block_states + .palette + .iter() + .position(|p| p.val == *block_id) + .unwrap_or_else(|| { + // Add block to palette if it doesn't exist + let index = section.block_states.palette.len() as i16; + section.block_states.palette.push((*block_id).into()); + index as usize + }); // Set block let blocks_per_i64 = (64f64 / bits_per_block as f64).floor() as usize; let index = ((y & 0xf) * 256 + (z & 0xf) * 16 + (x & 0xf)) as usize; @@ -164,9 +155,55 @@ impl World { e ))); } + // Remove empty palette entries + if remove_old_block { + debug!("Removing empty palette entry"); + // remove old block from palette + let old_block_id = BLOCK2ID.get(&old_block).unwrap(); + let old_block_palette_index = section + .block_states + .palette + .iter() + .position(|p| p.val == *old_block_id) + .expect("Old block not found in palette"); + section.block_states.palette.remove(old_block_palette_index); + // go through the block data and decrement the index of all blocks greater than the one we removed + for data in &mut section.block_states.data { + let mut i = 0; + while (i + bits_per_block as usize) < 64 { + let block_index = ferrumc_general_purpose::data_packing::u32::read_nbit_u32( + data, + bits_per_block as u8, + i as u32, + )?; + if block_index > old_block_palette_index as u32 { + ferrumc_general_purpose::data_packing::u32::write_nbit_u32( + data, + i as u32, + block_index - 1, + bits_per_block, + )?; + } + i += bits_per_block as usize; + } + } + } + + // Check if we need to resize bits_per_block + let new_bits_per_block = max( + (section.block_states.block_counts.len() as f32) + .log2() + .ceil() as u8, + 4, + ); + if new_bits_per_block != bits_per_block { + section.block_states.resize(new_bits_per_block as usize)?; + bits_per_block = new_bits_per_block; + } + section.block_states.bits_per_block = bits_per_block; + // Save chunk self.save_chunk(chunk).await?; - // TODO: Remove empty palette entries Ok(()) } } From f8812249f1ad79bd41211ee287bcd93c859e3180 Mon Sep 17 00:00:00 2001 From: ReCore Date: Tue, 21 Jan 2025 19:07:48 +1030 Subject: [PATCH 12/21] clippy --- src/lib/world/src/edits.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/world/src/edits.rs b/src/lib/world/src/edits.rs index 7c03f47a..a55dd88b 100644 --- a/src/lib/world/src/edits.rs +++ b/src/lib/world/src/edits.rs @@ -173,7 +173,7 @@ impl World { while (i + bits_per_block as usize) < 64 { let block_index = ferrumc_general_purpose::data_packing::u32::read_nbit_u32( data, - bits_per_block as u8, + bits_per_block, i as u32, )?; if block_index > old_block_palette_index as u32 { From d3f5bec06bc00777ec2b704a6896b5fffc7ab20e Mon Sep 17 00:00:00 2001 From: ReCore Date: Wed, 22 Jan 2025 14:59:56 +1030 Subject: [PATCH 13/21] Added collision checking and fixed double packet bug --- src/bin/src/packet_handlers/login_process.rs | 14 +- src/lib/core/Cargo.toml | 7 + src/lib/core/benches/collisions.rs | 56 ++++++ src/lib/core/benches/core_bench.rs | 9 + src/lib/core/src/collisions/bounds.rs | 170 ++++++++++++++++++ src/lib/core/src/collisions/mod.rs | 1 + src/lib/core/src/lib.rs | 1 + .../net/src/packets/incoming/place_block.rs | 110 ++++++++---- src/lib/world/src/edits.rs | 14 ++ 9 files changed, 349 insertions(+), 33 deletions(-) create mode 100644 src/lib/core/benches/collisions.rs create mode 100644 src/lib/core/benches/core_bench.rs create mode 100644 src/lib/core/src/collisions/bounds.rs create mode 100644 src/lib/core/src/collisions/mod.rs diff --git a/src/bin/src/packet_handlers/login_process.rs b/src/bin/src/packet_handlers/login_process.rs index ccf82297..9cf826bf 100644 --- a/src/bin/src/packet_handlers/login_process.rs +++ b/src/bin/src/packet_handlers/login_process.rs @@ -1,5 +1,6 @@ use ferrumc_config::statics::{get_global_config, get_whitelist}; use ferrumc_core::chunks::chunk_receiver::ChunkReceiver; +use ferrumc_core::collisions::bounds::CollisionBounds; use ferrumc_core::identity::player_identity::PlayerIdentity; use ferrumc_core::transform::grounded::OnGround; use ferrumc_core::transform::position::Position; @@ -152,7 +153,18 @@ async fn handle_ack_finish_configuration( .add_component::(entity_id, Position::default())? .add_component::(entity_id, Rotation::default())? .add_component::(entity_id, OnGround::default())? - .add_component::(entity_id, ChunkReceiver::default())?; + .add_component::(entity_id, ChunkReceiver::default())? + .add_component::( + entity_id, + CollisionBounds { + x_offset_start: -0.3, + x_offset_end: 0.3, + y_offset_start: -0.5, + y_offset_end: 1.5, + z_offset_start: -0.3, + z_offset_end: 0.3, + }, + )?; let mut writer = state.universe.get_mut::(entity_id)?; diff --git a/src/lib/core/Cargo.toml b/src/lib/core/Cargo.toml index 435b049c..e01b29f0 100644 --- a/src/lib/core/Cargo.toml +++ b/src/lib/core/Cargo.toml @@ -12,3 +12,10 @@ dashmap = { workspace = true } ferrumc-world = { workspace = true } tracing = { workspace = true } log = "0.4.22" + +[dev-dependencies] +criterion = { workspace = true } + +[[bench]] +name = "core_bench" +harness = false diff --git a/src/lib/core/benches/collisions.rs b/src/lib/core/benches/collisions.rs new file mode 100644 index 00000000..3bac8865 --- /dev/null +++ b/src/lib/core/benches/collisions.rs @@ -0,0 +1,56 @@ +use criterion::{black_box, Criterion}; +use ferrumc_core::collisions::bounds::CollisionBounds; + +pub fn bench_collides(c: &mut Criterion) { + let mut g = c.benchmark_group("collisions"); + g.bench_function("Simple collides", |b| { + b.iter(|| { + let bounds1 = black_box(CollisionBounds { + x_offset_start: 0.0, + x_offset_end: 2.0, + y_offset_start: 0.0, + y_offset_end: 2.0, + z_offset_start: 0.0, + z_offset_end: 2.0, + }); + let bounds2 = black_box(CollisionBounds { + x_offset_start: 1.0, + x_offset_end: 3.0, + y_offset_start: 1.0, + y_offset_end: 3.0, + z_offset_start: 1.0, + z_offset_end: 3.0, + }); + bounds1.collides( + black_box((0.0, 0.0, 0.0)), + &bounds2, + black_box((0.0, 0.0, 0.0)), + ); + }) + }); + g.bench_function("Complex collides", |b| { + b.iter(|| { + let bounds1 = black_box(CollisionBounds { + x_offset_start: 64.2, + x_offset_end: -8.4, + y_offset_start: -12.0, + y_offset_end: 16.3, + z_offset_start: 99.55, + z_offset_end: 100.999, + }); + let bounds2 = black_box(CollisionBounds { + x_offset_start: 5.0, + x_offset_end: 6.0, + y_offset_start: 1.0, + y_offset_end: 0.0, + z_offset_start: 2.0, + z_offset_end: 3.0, + }); + bounds1.collides( + black_box((12.0, 66.0, -5.0)), + &bounds2, + black_box((4444.0, -300.0, 0.1)), + ); + }) + }); +} diff --git a/src/lib/core/benches/core_bench.rs b/src/lib/core/benches/core_bench.rs new file mode 100644 index 00000000..b055d612 --- /dev/null +++ b/src/lib/core/benches/core_bench.rs @@ -0,0 +1,9 @@ +use criterion::{criterion_group, criterion_main, Criterion}; +mod collisions; + +fn bench(c: &mut Criterion) { + collisions::bench_collides(c); +} + +criterion_group!(benches, bench); +criterion_main!(benches); diff --git a/src/lib/core/src/collisions/bounds.rs b/src/lib/core/src/collisions/bounds.rs new file mode 100644 index 00000000..6a3d5330 --- /dev/null +++ b/src/lib/core/src/collisions/bounds.rs @@ -0,0 +1,170 @@ +pub struct CollisionBounds { + // Given a start position, where the bounding box starts on the x-axis. + pub x_offset_start: f64, + // Given a start position, where the bounding box ends on the x-axis. + pub x_offset_end: f64, + // Given a start position, where the bounding box starts on the y-axis. + pub y_offset_start: f64, + // Given a start position, where the bounding box ends on the y-axis. + pub y_offset_end: f64, + // Given a start position, where the bounding box starts on the z-axis. + pub z_offset_start: f64, + // Given a start position, where the bounding box ends on the z-axis. + pub z_offset_end: f64, +} + +impl Default for CollisionBounds { + fn default() -> Self { + CollisionBounds { + x_offset_start: 0.0, + x_offset_end: 0.0, + y_offset_start: 0.0, + y_offset_end: 0.0, + z_offset_start: 0.0, + z_offset_end: 0.0, + } + } +} + +impl CollisionBounds { + #[inline] + pub fn collides( + &self, + own_pos: (f64, f64, f64), + other_bounds: &CollisionBounds, + other_pos: (f64, f64, f64), + ) -> bool { + let (own_x, own_y, own_z) = own_pos; + let (other_x, other_y, other_z) = other_pos; + + // Pre-calculate bounds + let own_x_start = own_x + self.x_offset_start; + let own_x_end = own_x + self.x_offset_end; + let own_y_start = own_y + self.y_offset_start; + let own_y_end = own_y + self.y_offset_end; + let own_z_start = own_z + self.z_offset_start; + let own_z_end = own_z + self.z_offset_end; + + let other_x_start = other_x + other_bounds.x_offset_start; + let other_x_end = other_x + other_bounds.x_offset_end; + let other_y_start = other_y + other_bounds.y_offset_start; + let other_y_end = other_y + other_bounds.y_offset_end; + let other_z_start = other_z + other_bounds.z_offset_start; + let other_z_end = other_z + other_bounds.z_offset_end; + + // Check collisions axis by axis + (own_x_start < other_x_end && own_x_end > other_x_start) + && (own_y_start < other_y_end && own_y_end > other_y_start) + && (own_z_start < other_z_end && own_z_end > other_z_start) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn collides_when_boxes_overlap() { + let bounds1 = CollisionBounds { + x_offset_start: 0.0, + x_offset_end: 2.0, + y_offset_start: 0.0, + y_offset_end: 2.0, + z_offset_start: 0.0, + z_offset_end: 2.0, + }; + let bounds2 = CollisionBounds { + x_offset_start: 1.0, + x_offset_end: 3.0, + y_offset_start: 1.0, + y_offset_end: 3.0, + z_offset_start: 1.0, + z_offset_end: 3.0, + }; + assert!(bounds1.collides((0.0, 0.0, 0.0), &bounds2, (0.0, 0.0, 0.0))); + } + + #[test] + fn does_not_collide_when_boxes_do_not_overlap() { + let bounds1 = CollisionBounds { + x_offset_start: 0.0, + x_offset_end: 1.0, + y_offset_start: 0.0, + y_offset_end: 1.0, + z_offset_start: 0.0, + z_offset_end: 1.0, + }; + let bounds2 = CollisionBounds { + x_offset_start: 2.0, + x_offset_end: 3.0, + y_offset_start: 2.0, + y_offset_end: 3.0, + z_offset_start: 2.0, + z_offset_end: 3.0, + }; + assert!(!bounds1.collides((0.0, 0.0, 0.0), &bounds2, (0.0, 0.0, 0.0))); + } + + #[test] + fn collides_when_boxes_touch_edges() { + let bounds1 = CollisionBounds { + x_offset_start: 0.0, + x_offset_end: 1.0, + y_offset_start: 0.0, + y_offset_end: 1.0, + z_offset_start: 0.0, + z_offset_end: 1.0, + }; + let bounds2 = CollisionBounds { + x_offset_start: 1.0, + x_offset_end: 2.0, + y_offset_start: 1.0, + y_offset_end: 2.0, + z_offset_start: 1.0, + z_offset_end: 2.0, + }; + assert!(!bounds1.collides((0.0, 0.0, 0.0), &bounds2, (0.0, 0.0, 0.0))); + } + + #[test] + fn collides_when_one_box_inside_another() { + let bounds1 = CollisionBounds { + x_offset_start: 0.0, + x_offset_end: 3.0, + y_offset_start: 0.0, + y_offset_end: 3.0, + z_offset_start: 0.0, + z_offset_end: 3.0, + }; + let bounds2 = CollisionBounds { + x_offset_start: 1.0, + x_offset_end: 2.0, + y_offset_start: 1.0, + y_offset_end: 2.0, + z_offset_start: 1.0, + z_offset_end: 2.0, + }; + assert!(bounds1.collides((0.0, 0.0, 0.0), &bounds2, (0.0, 0.0, 0.0))); + } + + #[test] + fn does_not_collide_when_positions_are_far_apart() { + let bounds1 = CollisionBounds { + x_offset_start: 0.0, + x_offset_end: 1.0, + y_offset_start: 0.0, + y_offset_end: 1.0, + z_offset_start: 0.0, + z_offset_end: 1.0, + }; + let bounds2 = CollisionBounds { + x_offset_start: 0.0, + x_offset_end: 1.0, + y_offset_start: 0.0, + y_offset_end: 1.0, + z_offset_start: 0.0, + z_offset_end: 1.0, + }; + assert!(!bounds1.collides((0.0, 0.0, 0.0), &bounds2, (10.0, 10.0, 10.0))); + } +} diff --git a/src/lib/core/src/collisions/mod.rs b/src/lib/core/src/collisions/mod.rs new file mode 100644 index 00000000..c919e66b --- /dev/null +++ b/src/lib/core/src/collisions/mod.rs @@ -0,0 +1 @@ +pub mod bounds; diff --git a/src/lib/core/src/lib.rs b/src/lib/core/src/lib.rs index 93d6b8d4..cb752268 100644 --- a/src/lib/core/src/lib.rs +++ b/src/lib/core/src/lib.rs @@ -2,6 +2,7 @@ pub mod errors; // Core structs/types. Usually used in ECS Components. pub mod chunks; +pub mod collisions; pub mod identity; pub mod state; pub mod transform; diff --git a/src/lib/net/src/packets/incoming/place_block.rs b/src/lib/net/src/packets/incoming/place_block.rs index e4128182..458ed200 100644 --- a/src/lib/net/src/packets/incoming/place_block.rs +++ b/src/lib/net/src/packets/incoming/place_block.rs @@ -1,13 +1,15 @@ use crate::packets::IncomingPacket; use crate::NetResult; use ferrumc_core::chunks::chunk_receiver::ChunkReceiver; +use ferrumc_core::collisions::bounds::CollisionBounds; +use ferrumc_core::transform::position::Position; use ferrumc_macros::{packet, NetDecode}; use ferrumc_net_codec::net_types::network_position::NetworkPosition; use ferrumc_net_codec::net_types::var_int::VarInt; use ferrumc_state::ServerState; use ferrumc_world::vanilla_chunk_format::BlockData; use std::sync::Arc; -use tracing::debug; +use tracing::{debug, trace}; #[derive(NetDecode, Debug)] #[packet(packet_id = "use_item_on", state = "play")] @@ -24,37 +26,81 @@ pub struct PlaceBlock { impl IncomingPacket for PlaceBlock { async fn handle(self, _conn_id: usize, state: Arc) -> NetResult<()> { - let block_clicked = state - .clone() - .world - .get_block( - self.position.x, - self.position.y as i32, - self.position.z, - "overworld", - ) - .await?; - debug!("Block clicked: {:?}", block_clicked); - state - .world - .set_block( - self.position.x, - self.position.y as i32, - self.position.z, - "overworld", - BlockData { - name: "minecraft:stone".to_string(), - properties: None, - }, - ) - .await?; - let q = state.universe.query::<&mut ChunkReceiver>(); - for (_, mut chunk_recv) in q { - chunk_recv.queue_chunk_resend( - self.position.x >> 4, - self.position.z >> 4, - "overworld".to_string(), - ); + match self.hand.val { + 0 => { + debug!("Placing block at {:?}", self.position); + let block_clicked = state + .clone() + .world + .get_block( + self.position.x, + self.position.y as i32, + self.position.z, + "overworld", + ) + .await?; + trace!("Block clicked: {:?}", block_clicked); + // Use the face to determine the offset of the block to place + let (x_block_offset, y_block_offset, z_block_offset) = match self.face.val { + 0 => (0, -1, 0), + 1 => (0, 1, 0), + 2 => (0, 0, -1), + 3 => (0, 0, 1), + 4 => (-1, 0, 0), + 5 => (1, 0, 0), + _ => (0, 0, 0), + }; + let (x, y, z) = ( + self.position.x + x_block_offset, + self.position.y + y_block_offset, + self.position.z + z_block_offset, + ); + // Check if the block collides with any entities + let does_collide = { + let q = state.universe.query::<(&Position, &CollisionBounds)>(); + q.into_iter().any(|(_, (pos, bounds))| { + bounds.collides( + (pos.x, pos.y, pos.z), + &CollisionBounds { + x_offset_start: 0.0, + x_offset_end: 1.0, + y_offset_start: 0.0, + y_offset_end: 1.0, + z_offset_start: 0.0, + z_offset_end: 1.0, + }, + (x as f64, y as f64, z as f64), + ) + }) + }; + if does_collide { + debug!("Block placement collided with entity"); + return Ok(()); + } + state + .world + .set_block( + x, + y as i32, + z, + "overworld", + BlockData { + name: "minecraft:stone".to_string(), + properties: None, + }, + ) + .await?; + let q = state.universe.query::<&mut ChunkReceiver>(); + for (_, mut chunk_recv) in q { + chunk_recv.queue_chunk_resend(x >> 4, z >> 4, "overworld".to_string()); + } + } + 1 => { + debug!("Offhand block placement not implemented"); + } + _ => { + debug!("Invalid hand"); + } } Ok(()) } diff --git a/src/lib/world/src/edits.rs b/src/lib/world/src/edits.rs index a55dd88b..6bf50825 100644 --- a/src/lib/world/src/edits.rs +++ b/src/lib/world/src/edits.rs @@ -98,6 +98,7 @@ impl World { let mut bits_per_block = section.block_states.bits_per_block; // Remove old block let old_block = self.get_block(x, y, z, dimension).await?; + debug!("Old block: {:?}", old_block); let old_block_count = section .block_states .block_counts @@ -116,8 +117,20 @@ impl World { if let Some(e) = section.block_states.block_counts.get(&block) { section.block_states.block_counts.insert(block, e + 1); } else { + debug!("Adding block to block counts"); section.block_states.block_counts.insert(block, 1); } + let new_bits_per_block = max( + (section.block_states.block_counts.len() as f32) + .log2() + .ceil() as u8, + 4, + ); + if new_bits_per_block != bits_per_block { + debug!("Resizing block states to {}", new_bits_per_block); + section.block_states.resize(new_bits_per_block as usize)?; + bits_per_block = new_bits_per_block; + } // Get block index let block_palette_index = section .block_states @@ -197,6 +210,7 @@ impl World { 4, ); if new_bits_per_block != bits_per_block { + debug!("Resizing block states to {}", new_bits_per_block); section.block_states.resize(new_bits_per_block as usize)?; bits_per_block = new_bits_per_block; } From f0da9e7e7e92f77d007338464e34cc291ce638d7 Mon Sep 17 00:00:00 2001 From: ReCore Date: Thu, 30 Jan 2025 13:03:04 +1030 Subject: [PATCH 14/21] Fixed bug with missing palette entries --- scripts/new_packet.py | 1 - src/bin/src/packet_handlers/login_process.rs | 2 +- src/bin/src/systems/chunk_sender.rs | 7 - .../src/packets/incoming/chunk_batch_ack.rs | 2 +- .../net/src/packets/incoming/place_block.rs | 23 +- .../net/src/packets/incoming/player_action.rs | 2 +- .../src/packets/outgoing/block_change_ack.rs | 9 + src/lib/net/src/packets/outgoing/mod.rs | 2 + src/lib/world/src/chunk_format.rs | 260 +++++++++++++++++- src/lib/world/src/edits.rs | 192 ++----------- 10 files changed, 313 insertions(+), 187 deletions(-) create mode 100644 src/lib/net/src/packets/outgoing/block_change_ack.rs diff --git a/scripts/new_packet.py b/scripts/new_packet.py index 9db5e790..be28b21c 100644 --- a/scripts/new_packet.py +++ b/scripts/new_packet.py @@ -52,7 +52,6 @@ def to_camel_case(string) -> str: packet_id = input( "Packet ID (formatted as snake case, look on https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/Protocol if you need to get the id): ") -packet_id = packet_id[:-2] + packet_id[-2:].upper() with open(f"{packets_dir}/{packet_type}/{to_snake_case(packet_name)}.rs", "x") as f: if packet_type == "incoming": diff --git a/src/bin/src/packet_handlers/login_process.rs b/src/bin/src/packet_handlers/login_process.rs index 9cf826bf..77a807dc 100644 --- a/src/bin/src/packet_handlers/login_process.rs +++ b/src/bin/src/packet_handlers/login_process.rs @@ -159,7 +159,7 @@ async fn handle_ack_finish_configuration( CollisionBounds { x_offset_start: -0.3, x_offset_end: 0.3, - y_offset_start: -0.5, + y_offset_start: 0.0, y_offset_end: 1.5, z_offset_start: -0.3, z_offset_end: 0.3, diff --git a/src/bin/src/systems/chunk_sender.rs b/src/bin/src/systems/chunk_sender.rs index ffaed9d0..ecf41a35 100644 --- a/src/bin/src/systems/chunk_sender.rs +++ b/src/bin/src/systems/chunk_sender.rs @@ -76,13 +76,6 @@ impl System for ChunkSenderSystem { trace!("Got chunk_recv 3 for sender"); for (key, chunk) in chunk_recv.needed_chunks.iter_mut() { if let Some(chunk) = chunk { - chunk.sections.iter_mut().for_each(|section| { - // if random::() < 25 { - if let Err(e) = section.block_states.resize(8) { - error!("Error resizing block states: {:?}", e); - } - // } - }); to_drop.push(key.clone()); match ChunkAndLightData::from_chunk(&chunk.clone()) { Ok(packet) => { diff --git a/src/lib/net/src/packets/incoming/chunk_batch_ack.rs b/src/lib/net/src/packets/incoming/chunk_batch_ack.rs index 7d8d7695..11b997c1 100644 --- a/src/lib/net/src/packets/incoming/chunk_batch_ack.rs +++ b/src/lib/net/src/packets/incoming/chunk_batch_ack.rs @@ -39,7 +39,7 @@ impl IncomingPacket for ChunkBatchAck { let pos = state.universe.get_mut::(conn_id)?; let head_block = state .world - .get_block(pos.x as i32, pos.y as i32 - 1, pos.z as i32, "overworld") + .get_block_and_fetch(pos.x as i32, pos.y as i32 - 1, pos.z as i32, "overworld") .await?; if head_block.name == "minecraft:air" { move_to_spawn = false; diff --git a/src/lib/net/src/packets/incoming/place_block.rs b/src/lib/net/src/packets/incoming/place_block.rs index 458ed200..c25e4e99 100644 --- a/src/lib/net/src/packets/incoming/place_block.rs +++ b/src/lib/net/src/packets/incoming/place_block.rs @@ -1,9 +1,12 @@ +use crate::connection::StreamWriter; +use crate::packets::outgoing::block_change_ack::BlockChangeAck; use crate::packets::IncomingPacket; use crate::NetResult; use ferrumc_core::chunks::chunk_receiver::ChunkReceiver; use ferrumc_core::collisions::bounds::CollisionBounds; use ferrumc_core::transform::position::Position; use ferrumc_macros::{packet, NetDecode}; +use ferrumc_net_codec::encode::NetEncodeOpts; use ferrumc_net_codec::net_types::network_position::NetworkPosition; use ferrumc_net_codec::net_types::var_int::VarInt; use ferrumc_state::ServerState; @@ -25,14 +28,14 @@ pub struct PlaceBlock { } impl IncomingPacket for PlaceBlock { - async fn handle(self, _conn_id: usize, state: Arc) -> NetResult<()> { + async fn handle(self, conn_id: usize, state: Arc) -> NetResult<()> { match self.hand.val { 0 => { debug!("Placing block at {:?}", self.position); let block_clicked = state .clone() .world - .get_block( + .get_block_and_fetch( self.position.x, self.position.y as i32, self.position.z, @@ -74,12 +77,22 @@ impl IncomingPacket for PlaceBlock { }) }; if does_collide { - debug!("Block placement collided with entity"); + trace!("Block placement collided with entity"); return Ok(()); } + { + if let Ok(mut conn) = state.universe.get_mut::(conn_id) { + let packet = BlockChangeAck { + sequence: self.sequence.clone(), + }; + conn.send_packet(packet, &NetEncodeOpts::WithLength)?; + } else { + debug!("Could not get StreamWriter"); + } + } state .world - .set_block( + .set_block_and_fetch( x, y as i32, z, @@ -96,7 +109,7 @@ impl IncomingPacket for PlaceBlock { } } 1 => { - debug!("Offhand block placement not implemented"); + trace!("Offhand block placement not implemented"); } _ => { debug!("Invalid hand"); diff --git a/src/lib/net/src/packets/incoming/player_action.rs b/src/lib/net/src/packets/incoming/player_action.rs index bc5962b7..adea34f1 100644 --- a/src/lib/net/src/packets/incoming/player_action.rs +++ b/src/lib/net/src/packets/incoming/player_action.rs @@ -23,7 +23,7 @@ impl IncomingPacket for PlayerAction { 0 => { state .world - .set_block( + .set_block_and_fetch( self.location.x, self.location.y as i32, self.location.z, diff --git a/src/lib/net/src/packets/outgoing/block_change_ack.rs b/src/lib/net/src/packets/outgoing/block_change_ack.rs new file mode 100644 index 00000000..8b65934c --- /dev/null +++ b/src/lib/net/src/packets/outgoing/block_change_ack.rs @@ -0,0 +1,9 @@ +use ferrumc_macros::{packet, NetEncode}; +use ferrumc_net_codec::net_types::var_int::VarInt; +use std::io::Write; + +#[derive(NetEncode)] +#[packet(packet_id = "block_changed_ack", state = "play")] +pub struct BlockChangeAck { + pub sequence: VarInt, +} diff --git a/src/lib/net/src/packets/outgoing/mod.rs b/src/lib/net/src/packets/outgoing/mod.rs index ce630555..d14a7b6e 100644 --- a/src/lib/net/src/packets/outgoing/mod.rs +++ b/src/lib/net/src/packets/outgoing/mod.rs @@ -32,3 +32,5 @@ pub mod update_entity_position; pub mod update_entity_position_and_rotation; pub mod update_entity_rotation; // ----------------------------- + +pub mod block_change_ack; diff --git a/src/lib/world/src/chunk_format.rs b/src/lib/world/src/chunk_format.rs index 54fab67b..84e6ecfb 100644 --- a/src/lib/world/src/chunk_format.rs +++ b/src/lib/world/src/chunk_format.rs @@ -1,4 +1,5 @@ use crate::errors::WorldError; +use crate::errors::WorldError::InvalidBlockStateData; use crate::vanilla_chunk_format; use crate::vanilla_chunk_format::VanillaChunk; use bitcode_derive::{Decode, Encode}; @@ -10,7 +11,7 @@ use lazy_static::lazy_static; use std::cmp::max; use std::collections::HashMap; use std::io::Read; -use tracing::error; +use tracing::{debug, error, warn}; use vanilla_chunk_format::BlockData; #[cfg(test)] @@ -133,11 +134,14 @@ impl VanillaChunk { let mut i = 0; while i + bits_per_block < 64 { let palette_index = read_nbit_i32(chunk, bits_per_block as usize, i as u32)?; - let block = palette - .get(palette_index as usize) - .unwrap_or(&BlockData::default()) - .clone(); - *block_counts.entry(block).or_insert(0) += 1; + let block = match palette.get(palette_index as usize) { + Some(block) => block, + None => { + error!("Could not find block for palette index: {}", palette_index); + &BlockData::default() + } + }; + *block_counts.entry(block.clone()).or_insert(0) += 1; i += bits_per_block; } } @@ -149,6 +153,7 @@ impl VanillaChunk { }; block_counts.insert(single_block.clone(), 4096); } + // TODO: Void and cave air blocks are also counted as air blocks let non_air_blocks = 4096 - *block_counts.get(&BlockData::default()).unwrap_or(&0) as u16; let block_states = BlockStates { @@ -207,6 +212,10 @@ impl VanillaChunk { impl BlockStates { pub fn resize(&mut self, new_bit_size: usize) -> Result<(), WorldError> { + debug!( + "Resizing block states from {} to {} bits per block", + self.bits_per_block, new_bit_size + ); let max_int_value = (1 << new_bit_size) - 1; if self.data.is_empty() { @@ -293,3 +302,242 @@ impl BlockStates { Ok(()) } } + +impl Chunk { + /// Sets the block at the specified coordinates to the specified block data. + /// If the block is the same as the old block, nothing happens. + /// If the block is not in the palette, it is added. + /// If the palette is in single block mode, it is converted to palette'd mode. + /// + /// # Arguments + /// + /// * `x` - The x-coordinate of the block. + /// * `y` - The y-coordinate of the block. + /// * `z` - The z-coordinate of the block. + /// * `block` - The block data to set the block to. + /// + /// # Returns + /// + /// * `Ok(())` - If the block was successfully set. + /// * `Err(WorldError)` - If an error occurs while setting the block. + /// + /// ### Note + /// The positions are modulo'd by 16 to get the block index in the section anyway, so converting + /// the coordinates to section coordinates isn't really necessary, but you should probably do it + /// anyway for readability's sake. + pub fn set_block( + &mut self, + x: i32, + y: i32, + z: i32, + block: BlockData, + ) -> Result<(), WorldError> { + // Get old block + let old_block = self.get_block(x, y, z)?; + if old_block == block { + debug!("Block is the same as the old block"); + return Ok(()); + } + // Get section + let section = self + .sections + .iter_mut() + .find(|section| section.y == (y >> 4) as i8) + .ok_or(WorldError::SectionOutOfBounds(y >> 4))?; + // Since we've already checked if the blocks are the same, if the palette is in single block + // mode, we need to convert to palette'd mode + if section.block_states.palette.len() == 1 { + section.block_states.resize(4)?; + } + let bits_per_block = section.block_states.bits_per_block; + let block_counts = &mut section.block_states.block_counts; + match block_counts.get_mut(&old_block) { + Some(e) => { + if *e <= 0 { + return Err(WorldError::InvalidBlock(old_block)); + } + *e -= 1; + } + None => { + warn!("Block not found in block counts: {:?}", old_block); + } + } + let block_id = BLOCK2ID + .get(&block) + .ok_or(WorldError::InvalidBlock(block.clone()))?; + // Add new block + if let Some(e) = section.block_states.block_counts.get(&block) { + section.block_states.block_counts.insert(block, e + 1); + } else { + debug!("Adding block to block counts"); + section.block_states.block_counts.insert(block, 1); + } + // Get block index + let block_palette_index = section + .block_states + .palette + .iter() + .position(|p| p.val == *block_id) + .unwrap_or_else(|| { + // Add block to palette if it doesn't exist + let index = section.block_states.palette.len() as i16; + section.block_states.palette.push((*block_id).into()); + index as usize + }); + // Set block + let blocks_per_i64 = (64f64 / bits_per_block as f64).floor() as usize; + let index = ((y & 0xf) * 256 + (z & 0xf) * 16 + (x & 0xf)) as usize; + let i64_index = index / blocks_per_i64; + let packed_u64 = + section + .block_states + .data + .get_mut(i64_index) + .ok_or(InvalidBlockStateData(format!( + "Invalid block state data at index {}", + i64_index + )))?; + let offset = (index % blocks_per_i64) * bits_per_block as usize; + if let Err(e) = ferrumc_general_purpose::data_packing::u32::write_nbit_u32( + packed_u64, + offset as u32, + block_palette_index as u32, + bits_per_block, + ) { + return Err(InvalidBlockStateData(format!( + "Failed to write block: {}", + e + ))); + } + section.block_states.bits_per_block = bits_per_block; + Ok(()) + } + + /// Gets the block at the specified coordinates. + /// + /// # Arguments + /// + /// * `x` - The x-coordinate of the block. + /// * `y` - The y-coordinate of the block. + /// * `z` - The z-coordinate of the block. + /// + /// # Returns + /// + /// * `Ok(BlockData)` - The block data at the specified coordinates. + /// * `Err(WorldError)` - If an error occurs while retrieving the block data. + /// + /// ### Note + /// The positions are modulo'd by 16 to get the block index in the section anyway, so converting + /// the coordinates to section coordinates isn't really necessary, but you should probably do it + /// anyway for readability's sake. + pub fn get_block(&self, x: i32, y: i32, z: i32) -> Result { + let section = self + .sections + .iter() + .find(|section| section.y == (y >> 4) as i8) + .ok_or(WorldError::SectionOutOfBounds(y >> 4))?; + if section.block_states.palette.len() == 1 { + return ID2BLOCK + .get(§ion.block_states.palette[0].val) + .cloned() + .ok_or(WorldError::ChunkNotFound); + } + let bits_per_block = section.block_states.bits_per_block as usize; + let data = §ion.block_states.data; + let blocks_per_i64 = (64f64 / bits_per_block as f64).floor() as usize; + let index = ((y & 0xf) * 256 + (z & 0xf) * 16 + (x & 0xf)) as usize; + let i64_index = index / blocks_per_i64; + let packed_u64 = data + .get(i64_index) + .ok_or(WorldError::InvalidBlockStateData(format!( + "Invalid block state data at index {}", + i64_index + )))?; + let offset = (index % blocks_per_i64) * bits_per_block; + let id = ferrumc_general_purpose::data_packing::u32::read_nbit_u32( + packed_u64, + bits_per_block as u8, + offset as u32, + )?; + let palette_id = section + .block_states + .palette + .get(id as usize) + .ok_or(WorldError::ChunkNotFound)?; + Ok(crate::chunk_format::ID2BLOCK + .get(&palette_id.val) + .unwrap_or(&BlockData::default()) + .clone()) + } +} + +impl Section { + /// This function trims out unnecessary data from the section. Primarily it does 2 things: + /// + /// 1. Removes any palette entries that are not used in the block states data. + /// + /// 2. If there is only one block in the palette, it converts the palette to single block mode. + pub fn optimise(&mut self) -> Result<(), WorldError> { + { + // Remove empty blocks from palette + let mut remove_indexes = Vec::new(); + for (block, count) in &self.block_states.block_counts { + if *count <= 0 { + let block_id = BLOCK2ID + .get(block) + .ok_or(WorldError::InvalidBlock(block.clone()))?; + let index = self + .block_states + .palette + .iter() + .position(|p| p.val == *block_id); + if let Some(index) = index { + remove_indexes.push(index); + } else { + return Err(WorldError::InvalidBlock(block.clone())); + } + } + } + for index in remove_indexes { + // Decrement any data entries that are higher than the removed index + for data in &mut self.block_states.data { + let mut i = 0; + while (i + self.block_states.bits_per_block as usize) < 64 { + let block_index = + ferrumc_general_purpose::data_packing::u32::read_nbit_u32( + data, + self.block_states.bits_per_block, + i as u32, + )?; + if block_index > index as u32 { + ferrumc_general_purpose::data_packing::u32::write_nbit_u32( + data, + i as u32, + block_index - 1, + self.block_states.bits_per_block, + )?; + } + i += self.block_states.bits_per_block as usize; + } + } + } + } + { + // If there is only one block in the palette, remove the palette and set the block to the first entry + if self.block_states.palette.len() == 1 { + let block_id = self.block_states.palette[0].val; + let block = ID2BLOCK + .get(&block_id) + .cloned() + .unwrap_or(BlockData::default()); + self.block_states.palette.clear(); + self.block_states.palette.push(VarInt::from(block_id)); + self.block_states.data.clear(); + self.block_states.block_counts.clear(); + self.block_states.block_counts.insert(block, 4096); + } + } + + Ok(()) + } +} diff --git a/src/lib/world/src/edits.rs b/src/lib/world/src/edits.rs index 6bf50825..bad1e41c 100644 --- a/src/lib/world/src/edits.rs +++ b/src/lib/world/src/edits.rs @@ -1,13 +1,13 @@ -use crate::chunk_format::{BLOCK2ID, ID2BLOCK}; +use crate::chunk_format::BLOCK2ID; use crate::errors::WorldError; -use crate::errors::WorldError::InvalidBlockStateData; use crate::vanilla_chunk_format::BlockData; use crate::World; -use std::cmp::max; use tracing::debug; impl World { /// Asynchronously retrieves the block data at the specified coordinates in the given dimension. + /// Under the hood, this function just fetches the chunk containing the block and then calls + /// [`crate::chunk_format::Chunk::get_block`] on it. /// /// # Arguments /// @@ -26,7 +26,7 @@ impl World { /// * `WorldError::SectionOutOfBounds` - If the section containing the block is out of bounds. /// * `WorldError::ChunkNotFound` - If the chunk or block data is not found. /// * `WorldError::InvalidBlockStateData` - If the block state data is invalid. - pub async fn get_block( + pub async fn get_block_and_fetch( &self, x: i32, y: i32, @@ -36,46 +36,26 @@ impl World { let chunk_x = x >> 4; let chunk_z = z >> 4; let chunk = self.load_chunk(chunk_x, chunk_z, dimension).await?; - let section = chunk - .sections - .iter() - .find(|section| section.y == (y >> 4) as i8) - .ok_or(WorldError::SectionOutOfBounds(y >> 4))?; - if section.block_states.palette.len() == 1 { - return ID2BLOCK - .get(§ion.block_states.palette[0].val) - .cloned() - .ok_or(WorldError::ChunkNotFound); - } - let bits_per_block = section.block_states.bits_per_block as usize; - let data = §ion.block_states.data; - let blocks_per_i64 = (64f64 / bits_per_block as f64).floor() as usize; - let index = ((y & 0xf) * 256 + (z & 0xf) * 16 + (x & 0xf)) as usize; - let i64_index = index / blocks_per_i64; - let packed_u64 = data - .get(i64_index) - .ok_or(WorldError::InvalidBlockStateData(format!( - "Invalid block state data at index {}", - i64_index - )))?; - let offset = (index % blocks_per_i64) * bits_per_block; - let id = ferrumc_general_purpose::data_packing::u32::read_nbit_u32( - packed_u64, - bits_per_block as u8, - offset as u32, - )?; - let palette_id = section - .block_states - .palette - .get(id as usize) - .ok_or(WorldError::ChunkNotFound)?; - Ok(crate::chunk_format::ID2BLOCK - .get(&palette_id.val) - .unwrap_or(&BlockData::default()) - .clone()) + chunk.get_block(x, y, z) } - pub async fn set_block( + /// Asynchronously sets the block data at the specified coordinates in the given dimension. + /// Under the hood, this function just fetches the chunk containing the block and then calls + /// [`crate::chunk_format::Chunk::set_block`] on it. + /// + /// # Arguments + /// + /// * `x` - The x-coordinate of the block. + /// * `y` - The y-coordinate of the block. + /// * `z` - The z-coordinate of the block. + /// * `dimension` - The dimension in which the block is located. + /// * `block` - The block data to set. + /// + /// # Returns + /// + /// * `Ok(())` - If the block data is successfully set. + /// * `Err(WorldError)` - If an error occurs while setting the block data. + pub async fn set_block_and_fetch( &self, x: i32, y: i32, @@ -90,131 +70,13 @@ impl World { let chunk_x = x >> 4; let chunk_z = z >> 4; let mut chunk = self.load_chunk(chunk_x, chunk_z, dimension).await?; - let section = chunk - .sections - .iter_mut() - .find(|section| section.y == (y >> 4) as i8) - .ok_or(WorldError::SectionOutOfBounds(y >> 4))?; - let mut bits_per_block = section.block_states.bits_per_block; - // Remove old block - let old_block = self.get_block(x, y, z, dimension).await?; - debug!("Old block: {:?}", old_block); - let old_block_count = section - .block_states - .block_counts - .get_mut(&old_block) - .expect("Block not found"); - *old_block_count -= 1; - let mut remove_old_block = false; - if *old_block_count <= 0 { - section.block_states.block_counts.remove(&old_block); - remove_old_block = true; - } - let block_id = BLOCK2ID - .get(&block) - .ok_or(WorldError::InvalidBlock(block.clone()))?; - // Add new block - if let Some(e) = section.block_states.block_counts.get(&block) { - section.block_states.block_counts.insert(block, e + 1); - } else { - debug!("Adding block to block counts"); - section.block_states.block_counts.insert(block, 1); - } - let new_bits_per_block = max( - (section.block_states.block_counts.len() as f32) - .log2() - .ceil() as u8, - 4, - ); - if new_bits_per_block != bits_per_block { - debug!("Resizing block states to {}", new_bits_per_block); - section.block_states.resize(new_bits_per_block as usize)?; - bits_per_block = new_bits_per_block; - } - // Get block index - let block_palette_index = section - .block_states - .palette - .iter() - .position(|p| p.val == *block_id) - .unwrap_or_else(|| { - // Add block to palette if it doesn't exist - let index = section.block_states.palette.len() as i16; - section.block_states.palette.push((*block_id).into()); - index as usize - }); - // Set block - let blocks_per_i64 = (64f64 / bits_per_block as f64).floor() as usize; - let index = ((y & 0xf) * 256 + (z & 0xf) * 16 + (x & 0xf)) as usize; - let i64_index = index / blocks_per_i64; - let packed_u64 = - section - .block_states - .data - .get_mut(i64_index) - .ok_or(InvalidBlockStateData(format!( - "Invalid block state data at index {}", - i64_index - )))?; - let offset = (index % blocks_per_i64) * bits_per_block as usize; - if let Err(e) = ferrumc_general_purpose::data_packing::u32::write_nbit_u32( - packed_u64, - offset as u32, - block_palette_index as u32, - bits_per_block, - ) { - return Err(InvalidBlockStateData(format!( - "Failed to write block: {}", - e - ))); - } - // Remove empty palette entries - if remove_old_block { - debug!("Removing empty palette entry"); - // remove old block from palette - let old_block_id = BLOCK2ID.get(&old_block).unwrap(); - let old_block_palette_index = section - .block_states - .palette - .iter() - .position(|p| p.val == *old_block_id) - .expect("Old block not found in palette"); - section.block_states.palette.remove(old_block_palette_index); - // go through the block data and decrement the index of all blocks greater than the one we removed - for data in &mut section.block_states.data { - let mut i = 0; - while (i + bits_per_block as usize) < 64 { - let block_index = ferrumc_general_purpose::data_packing::u32::read_nbit_u32( - data, - bits_per_block, - i as u32, - )?; - if block_index > old_block_palette_index as u32 { - ferrumc_general_purpose::data_packing::u32::write_nbit_u32( - data, - i as u32, - block_index - 1, - bits_per_block, - )?; - } - i += bits_per_block as usize; - } - } - } - // Check if we need to resize bits_per_block - let new_bits_per_block = max( - (section.block_states.block_counts.len() as f32) - .log2() - .ceil() as u8, - 4, - ); - if new_bits_per_block != bits_per_block { - debug!("Resizing block states to {}", new_bits_per_block); - section.block_states.resize(new_bits_per_block as usize)?; - bits_per_block = new_bits_per_block; + debug!("Chunk: {}, {}", chunk_x, chunk_z); + + chunk.set_block(x, y, z, block)?; + for section in &mut chunk.sections { + section.optimise()?; } - section.block_states.bits_per_block = bits_per_block; // Save chunk self.save_chunk(chunk).await?; From 01d8d4d0a6dbdf6f50d12cd84466a6d3b9cf0c82 Mon Sep 17 00:00:00 2001 From: ReCore Date: Thu, 30 Jan 2025 13:17:25 +1030 Subject: [PATCH 15/21] Clippy --- .../net/crates/codec/src/net_types/bitset.rs | 2 +- src/lib/utils/config/src/server_config.rs | 10 +++++----- src/lib/utils/config/src/whitelist.rs | 5 +---- .../general_purpose/src/data_packing/i32.rs | 2 +- src/lib/world/src/chunk_format.rs | 20 +++++++++---------- 5 files changed, 17 insertions(+), 22 deletions(-) diff --git a/src/lib/net/crates/codec/src/net_types/bitset.rs b/src/lib/net/crates/codec/src/net_types/bitset.rs index 2e7e8f12..45354cc1 100644 --- a/src/lib/net/crates/codec/src/net_types/bitset.rs +++ b/src/lib/net/crates/codec/src/net_types/bitset.rs @@ -9,7 +9,7 @@ pub struct BitSet(Vec); impl BitSet { pub fn new(size: usize) -> Self { - let num_blocks = (size + 63) / 64; + let num_blocks = size.div_ceil(64); Self(vec![0; num_blocks]) } diff --git a/src/lib/utils/config/src/server_config.rs b/src/lib/utils/config/src/server_config.rs index 127e2433..bc1392b7 100644 --- a/src/lib/utils/config/src/server_config.rs +++ b/src/lib/utils/config/src/server_config.rs @@ -34,14 +34,14 @@ pub struct ServerConfig { /// Fields: /// - `cache_size`: The cache size in KB. /// - `compression` - Which compression algorithm to use. Options are `brotli`, `deflate`, `gzip`, `zlib` -/// and `zstd` +/// and `zstd` /// - `world_path`: The path to the world database. /// - `compression_level`: The compression level to use. This is a number from 0-22. Not all compressors -/// support levels, so this will be a no-op for some compressors. +/// support levels, so this will be a no-op for some compressors. /// - `map_size`: The max size of the database's memory map. Basically you need this to be big enough -/// to hold everything before it starts writing to disk. This isn't memory use though, it's just -/// how much we can map into memory if needed, so you can set this to an insane number if you want, -/// but it won't actually use that much memory, it'll just show up as virtual memory use. +/// to hold everything before it starts writing to disk. This isn't memory use though, it's just +/// how much we can map into memory if needed, so you can set this to an insane number if you want, +/// but it won't actually use that much memory, it'll just show up as virtual memory use. /// - `cache_ttl`: The time to live for cache entries in seconds. /// - `cache_capacity`: How big the cache can be in kb. #[derive(Debug, Deserialize, Serialize)] diff --git a/src/lib/utils/config/src/whitelist.rs b/src/lib/utils/config/src/whitelist.rs index ffc268ad..f73b28eb 100644 --- a/src/lib/utils/config/src/whitelist.rs +++ b/src/lib/utils/config/src/whitelist.rs @@ -186,10 +186,7 @@ async fn query_mojang_for_usernames(uuids: Vec<&Uuid>) -> Vec { match response { Ok(response) if response.status().is_success() => { - match response.json::().await { - Ok(parsed_response) => Some(parsed_response), - Err(_) => None, - } + response.json::().await.ok() } _ => None, } diff --git a/src/lib/utils/general_purpose/src/data_packing/i32.rs b/src/lib/utils/general_purpose/src/data_packing/i32.rs index aecbde08..41ec07da 100644 --- a/src/lib/utils/general_purpose/src/data_packing/i32.rs +++ b/src/lib/utils/general_purpose/src/data_packing/i32.rs @@ -17,7 +17,7 @@ use crate::data_packing::errors::DataPackingError; /// /// * `DataPackingError::SizeExceedsMaxSize` - If `size` is greater than 32. /// * `DataPackingError::NotEnoughBits` - If `offset + size` exceeds 64 bits. -/// Reads an n-bit integer from a packed `i64`. +/// Reads an n-bit integer from a packed `i64`. pub fn read_nbit_i32( word: &i64, bit_size: usize, diff --git a/src/lib/world/src/chunk_format.rs b/src/lib/world/src/chunk_format.rs index 84e6ecfb..05e48e51 100644 --- a/src/lib/world/src/chunk_format.rs +++ b/src/lib/world/src/chunk_format.rs @@ -219,7 +219,7 @@ impl BlockStates { let max_int_value = (1 << new_bit_size) - 1; if self.data.is_empty() { - let data_size = (4096 * new_bit_size + 63) / 64; + let data_size = (4096 * new_bit_size).div_ceil(64); self.data = vec![0; data_size]; self.bits_per_block = new_bit_size as u8; return Ok(()); @@ -240,7 +240,7 @@ impl BlockStates { // Extract value at the current bit offset let value = read_nbit_i32(&long, self.bits_per_block as usize, bit_offset as u32)?; if value > max_int_value { - return Err(WorldError::InvalidBlockStateData(format!( + return Err(InvalidBlockStateData(format!( "Value {} exceeds maximum value for {}-bit block state", value, new_bit_size ))); @@ -259,7 +259,7 @@ impl BlockStates { // Check if we read exactly 4096 block states if normalised_ints.len() != 4096 { - return Err(WorldError::InvalidBlockStateData(format!( + return Err(InvalidBlockStateData(format!( "Expected 4096 block states, but got {}", normalised_ints.len() ))); @@ -287,9 +287,9 @@ impl BlockStates { } // Verify the size of the new data matches expectations - let expected_size = (4096 * new_bit_size + 63) / 64; + let expected_size = (4096 * new_bit_size).div_ceil(64); if new_data.len() != expected_size { - return Err(WorldError::InvalidBlockStateData(format!( + return Err(InvalidBlockStateData(format!( "Expected packed data size of {}, but got {}", expected_size, new_data.len() @@ -447,12 +447,10 @@ impl Chunk { let blocks_per_i64 = (64f64 / bits_per_block as f64).floor() as usize; let index = ((y & 0xf) * 256 + (z & 0xf) * 16 + (x & 0xf)) as usize; let i64_index = index / blocks_per_i64; - let packed_u64 = data - .get(i64_index) - .ok_or(WorldError::InvalidBlockStateData(format!( - "Invalid block state data at index {}", - i64_index - )))?; + let packed_u64 = data.get(i64_index).ok_or(InvalidBlockStateData(format!( + "Invalid block state data at index {}", + i64_index + )))?; let offset = (index % blocks_per_i64) * bits_per_block; let id = ferrumc_general_purpose::data_packing::u32::read_nbit_u32( packed_u64, From 78b8b31988e70ddf5d1c0831224e784d102027b1 Mon Sep 17 00:00:00 2001 From: ReCore Date: Thu, 30 Jan 2025 13:34:06 +1030 Subject: [PATCH 16/21] Acknowledge block breaking --- .../net/src/packets/incoming/player_action.rs | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/src/lib/net/src/packets/incoming/player_action.rs b/src/lib/net/src/packets/incoming/player_action.rs index adea34f1..4b163642 100644 --- a/src/lib/net/src/packets/incoming/player_action.rs +++ b/src/lib/net/src/packets/incoming/player_action.rs @@ -1,6 +1,10 @@ +use crate::connection::StreamWriter; +use crate::packets::outgoing::block_change_ack::BlockChangeAck; use crate::packets::IncomingPacket; use crate::NetResult; +use ferrumc_core::chunks::chunk_receiver::ChunkReceiver; use ferrumc_macros::{packet, NetDecode}; +use ferrumc_net_codec::encode::NetEncodeOpts; use ferrumc_net_codec::net_types::network_position::NetworkPosition; use ferrumc_net_codec::net_types::var_int::VarInt; use ferrumc_state::ServerState; @@ -17,7 +21,7 @@ pub struct PlayerAction { } impl IncomingPacket for PlayerAction { - async fn handle(self, _: usize, state: Arc) -> NetResult<()> { + async fn handle(self, conn_id: usize, state: Arc) -> NetResult<()> { // https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/Protocol?oldid=2773393#Player_Action match self.status.val { 0 => { @@ -34,6 +38,28 @@ impl IncomingPacket for PlayerAction { }, ) .await?; + { + let packet = BlockChangeAck { + sequence: self.sequence, + }; + if let Ok(mut conn) = state.universe.get_mut::<&mut StreamWriter>(conn_id) { + conn.send_packet(packet, &NetEncodeOpts::WithLength)?; + } else { + debug!( + "Player disconnected before we could send the BlockChangeAck packet" + ); + } + } + { + let q = state.universe.query::<&mut ChunkReceiver>(); + for (_, mut chunk_receiver) in q { + chunk_receiver.queue_chunk_resend( + self.location.x >> 4, + self.location.z >> 4, + "overworld".to_string(), + ); + } + } } 1 => { debug!("You shouldn't be seeing this in creative mode."); From c189603e4417220ec0915e84ee4f55287bea8042 Mon Sep 17 00:00:00 2001 From: ReCore Date: Thu, 30 Jan 2025 17:48:53 +1030 Subject: [PATCH 17/21] Basic world gen, but block edits don't work on generated worlds for some reason --- src/bin/Cargo.toml | 1 + src/bin/src/systems/chunk_fetcher.rs | 29 +++- .../net/src/packets/incoming/place_block.rs | 25 ++- .../net/src/packets/incoming/player_action.rs | 29 ++-- src/lib/storage/src/lmdb.rs | 16 +- src/lib/world/src/chunk_format.rs | 150 +++++++++++++++--- src/lib/world/src/db_functions.rs | 9 ++ 7 files changed, 210 insertions(+), 49 deletions(-) diff --git a/src/bin/Cargo.toml b/src/bin/Cargo.toml index 1451fff7..1e758287 100644 --- a/src/bin/Cargo.toml +++ b/src/bin/Cargo.toml @@ -35,6 +35,7 @@ async-trait = { workspace = true } clap = { workspace = true, features = ["derive"] } flate2 = { workspace = true } ctor = { workspace = true } +log = "0.4.22" [[bin]] diff --git a/src/bin/src/systems/chunk_fetcher.rs b/src/bin/src/systems/chunk_fetcher.rs index e219db8e..1f98b011 100644 --- a/src/bin/src/systems/chunk_fetcher.rs +++ b/src/bin/src/systems/chunk_fetcher.rs @@ -3,7 +3,10 @@ use crate::systems::definition::System; use async_trait::async_trait; use ferrumc_core::chunks::chunk_receiver::ChunkReceiver; use ferrumc_state::GlobalState; -use std::collections::HashMap; +use ferrumc_world::chunk_format::Chunk; +use ferrumc_world::errors::WorldError; +use ferrumc_world::vanilla_chunk_format::BlockData; +use std::collections::{BTreeMap, HashMap}; use std::sync::atomic::AtomicBool; use std::sync::Arc; use tokio::task::JoinSet; @@ -49,8 +52,28 @@ impl System for ChunkFetcher { }; // Fetch the chunks for (key, chunk) in copied_chunks.iter_mut() { - let fetched_chunk = - state.world.load_chunk(key.0, key.1, &key.2.clone()).await?; + let fetched_chunk = if state + .world + .chunk_exists(key.0, key.1, &key.2.clone()) + .await? + { + debug!("Chunk found, loading chunk"); + state.world.load_chunk(key.0, key.1, &key.2.clone()).await? + } else { + debug!("Chunk not found, creating new chunk"); + let mut new_chunk = Chunk::new(key.0, key.1, key.2.clone()); + for section in 0..8 { + new_chunk.set_section( + section, + BlockData { + name: "minecraft:grass_block".to_string(), + properties: Some(BTreeMap::from([("snowy".to_string(), "false".to_string())])), + }, + )?; + } + state.world.save_chunk(new_chunk.clone()).await?; + new_chunk + }; *chunk = Some(fetched_chunk); } // Insert the fetched chunks back into the component diff --git a/src/lib/net/src/packets/incoming/place_block.rs b/src/lib/net/src/packets/incoming/place_block.rs index c25e4e99..283edd29 100644 --- a/src/lib/net/src/packets/incoming/place_block.rs +++ b/src/lib/net/src/packets/incoming/place_block.rs @@ -90,19 +90,18 @@ impl IncomingPacket for PlaceBlock { debug!("Could not get StreamWriter"); } } - state - .world - .set_block_and_fetch( - x, - y as i32, - z, - "overworld", - BlockData { - name: "minecraft:stone".to_string(), - properties: None, - }, - ) - .await?; + let mut chunk = state.world.load_chunk(x >> 4, z >> 4, "overworld").await?; + + chunk.set_block( + x & 0xF, + y as i32, + z & 0xF, + BlockData { + name: "minecraft:stone".to_string(), + properties: None, + }, + )?; + state.world.save_chunk(chunk).await?; let q = state.universe.query::<&mut ChunkReceiver>(); for (_, mut chunk_recv) in q { chunk_recv.queue_chunk_resend(x >> 4, z >> 4, "overworld".to_string()); diff --git a/src/lib/net/src/packets/incoming/player_action.rs b/src/lib/net/src/packets/incoming/player_action.rs index 4b163642..640f5714 100644 --- a/src/lib/net/src/packets/incoming/player_action.rs +++ b/src/lib/net/src/packets/incoming/player_action.rs @@ -10,6 +10,7 @@ use ferrumc_net_codec::net_types::var_int::VarInt; use ferrumc_state::ServerState; use std::sync::Arc; use tracing::debug; +use ferrumc_world::vanilla_chunk_format::BlockData; #[derive(NetDecode)] #[packet(packet_id = "player_action", state = "play")] @@ -25,24 +26,28 @@ impl IncomingPacket for PlayerAction { // https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/Protocol?oldid=2773393#Player_Action match self.status.val { 0 => { - state + let mut chunk = state + .clone() .world - .set_block_and_fetch( - self.location.x, - self.location.y as i32, - self.location.z, + .load_chunk( + self.location.x >> 4, + self.location.z >> 4, "overworld", - ferrumc_world::vanilla_chunk_format::BlockData { - name: "minecraft:air".to_string(), - properties: None, - }, - ) - .await?; + ).await?; + chunk.set_block( + self.location.x, + self.location.y as i32, + self.location.z, + BlockData::default(), + )?; + state.world.save_chunk( + chunk + ).await?; { let packet = BlockChangeAck { sequence: self.sequence, }; - if let Ok(mut conn) = state.universe.get_mut::<&mut StreamWriter>(conn_id) { + if let Ok(mut conn) = state.universe.get_mut::(conn_id) { conn.send_packet(packet, &NetEncodeOpts::WithLength)?; } else { debug!( diff --git a/src/lib/storage/src/lmdb.rs b/src/lib/storage/src/lmdb.rs index 2224b1c4..af7361d5 100644 --- a/src/lib/storage/src/lmdb.rs +++ b/src/lib/storage/src/lmdb.rs @@ -44,7 +44,7 @@ impl LmdbBackend { let rounded_map_size = ((map_size as f64 / page_size::get() as f64).round() * page_size::get() as f64) as usize; unsafe { - Ok(LmdbBackend { + let backend = LmdbBackend { env: Arc::new( EnvOpenOptions::new() // Change this as more tables are needed. @@ -53,7 +53,8 @@ impl LmdbBackend { .open(checked_path) .map_err(|e| StorageError::DatabaseInitError(e.to_string()))?, ), - }) + }; + Ok(backend) } } @@ -209,6 +210,17 @@ impl LmdbBackend { .await .expect("Failed to run tokio task") } + + pub async fn table_exists(&self, table: String) -> Result { + let env = self.env.clone(); + tokio::task::spawn_blocking(move || { + let ro_txn = env.read_txn()?; + let db = env.open_database::, Bytes>(&ro_txn, Some(&table))?; + Ok(db.is_some()) + }) + .await + .expect("Failed to run tokio task") + } pub async fn details(&self) -> String { format!("LMDB (heed 0.20.5): {:?}", self.env.info()) diff --git a/src/lib/world/src/chunk_format.rs b/src/lib/world/src/chunk_format.rs index 05e48e51..51bfbe89 100644 --- a/src/lib/world/src/chunk_format.rs +++ b/src/lib/world/src/chunk_format.rs @@ -62,8 +62,7 @@ pub struct Heightmaps { pub struct Section { pub y: i8, pub block_states: BlockStates, - pub biome_data: Vec, - pub biome_palette: Vec, + pub biome_states: BiomeStates, pub block_light: Vec, pub sky_light: Vec, } @@ -76,6 +75,13 @@ pub struct BlockStates { pub block_counts: HashMap, } +#[derive(Encode, Decode, Clone, DeepSizeOf)] +pub struct BiomeStates { + pub bits_per_biome: u8, + pub data: Vec, + pub palette: Vec, +} + fn convert_to_net_palette(vanilla_palettes: Vec) -> Result, WorldError> { let mut new_palette = Vec::new(); for palette in vanilla_palettes { @@ -119,15 +125,6 @@ impl VanillaChunk { .as_ref() .and_then(|bs| bs.palette.clone()) .unwrap_or_default(); - let biome_data = section - .biomes - .as_ref() - .and_then(|biome_data| biome_data.data.clone()) - .unwrap_or_default(); - let biome_palette = section - .biomes - .as_ref() - .map_or(vec![], |biome_data| biome_data.palette.clone()); let bits_per_block = max((palette.len() as f32).log2().ceil() as u8, 4); let mut block_counts = HashMap::new(); for chunk in &block_data { @@ -153,9 +150,21 @@ impl VanillaChunk { }; block_counts.insert(single_block.clone(), 4096); } - // TODO: Void and cave air blocks are also counted as air blocks - let non_air_blocks = - 4096 - *block_counts.get(&BlockData::default()).unwrap_or(&0) as u16; + // Count the number of blocks that are either air, void air, or cave air + let mut air_blocks = *block_counts.get(&BlockData::default()).unwrap_or(&0) as u16; + air_blocks += *block_counts + .get(&BlockData { + name: "minecraft:void_air".to_string(), + properties: None, + }) + .unwrap_or(&0) as u16; + air_blocks += *block_counts + .get(&BlockData { + name: "minecraft:cave_air".to_string(), + properties: None, + }) + .unwrap_or(&0) as u16; + let non_air_blocks = 4096 - air_blocks; let block_states = BlockStates { bits_per_block, block_counts, @@ -177,11 +186,16 @@ impl VanillaChunk { .iter() .map(|x| *x as u8) .collect(); + let biome_states = BiomeStates { + // TODO: Implement biome states properly + bits_per_biome: 4, + data: vec![], + palette: vec![VarInt::from(0); 1], + }; let section = Section { y, block_states, - biome_data, - biome_palette, + biome_states, block_light, sky_light, }; @@ -346,7 +360,7 @@ impl Chunk { .ok_or(WorldError::SectionOutOfBounds(y >> 4))?; // Since we've already checked if the blocks are the same, if the palette is in single block // mode, we need to convert to palette'd mode - if section.block_states.palette.len() == 1 { + if section.block_states.palette.len() == 1 || section.block_states.bits_per_block == 0 { section.block_states.resize(4)?; } let bits_per_block = section.block_states.bits_per_block; @@ -436,13 +450,13 @@ impl Chunk { .iter() .find(|section| section.y == (y >> 4) as i8) .ok_or(WorldError::SectionOutOfBounds(y >> 4))?; - if section.block_states.palette.len() == 1 { + let bits_per_block = section.block_states.bits_per_block as usize; + if section.block_states.palette.len() == 1 || bits_per_block == 0 { return ID2BLOCK .get(§ion.block_states.palette[0].val) .cloned() .ok_or(WorldError::ChunkNotFound); } - let bits_per_block = section.block_states.bits_per_block as usize; let data = §ion.block_states.data; let blocks_per_i64 = (64f64 / bits_per_block as f64).floor() as usize; let index = ((y & 0xf) * 256 + (z & 0xf) * 16 + (x & 0xf)) as usize; @@ -467,9 +481,107 @@ impl Chunk { .unwrap_or(&BlockData::default()) .clone()) } + + pub fn new(x: i32, z: i32, dimension: String) -> Self { + let mut sections: Vec
= (0..24) + .map(|y| Section { + y: y as i8, + block_states: BlockStates { + bits_per_block: 4, + non_air_blocks: 0, + data: vec![0; 256], + palette: vec![VarInt::from(0)], + block_counts: HashMap::from([(BlockData::default(), 4096)]), + }, + biome_states: BiomeStates { + bits_per_biome: 4, + data: vec![], + palette: vec![VarInt::from(0)], + }, + block_light: vec![255; 2048], + sky_light: vec![255; 2048], + }) + .collect(); + for section in &mut sections { + section.optimise().expect("Failed to optimise section"); + } + debug!(seclen = sections.len()); + Chunk { + x, + z, + dimension, + sections, + heightmaps: Heightmaps::new(), + } + } + + /// Sets the section at the specified index to the specified block data. + /// If the section is out of bounds, an error is returned. + /// + /// # Arguments + /// + /// * `section` - The index of the section to set. + /// * `block` - The block data to set the section to. + /// + /// # Returns + /// + /// * `Ok(())` - If the section was successfully set. + /// * `Err(WorldError)` - If an error occurs while setting the section. + pub fn set_section(&mut self, section: u8, block: BlockData) -> Result<(), WorldError> { + if let Some(section) = self.sections.get_mut(section as usize) { + section.fill(block) + } else { + Err(WorldError::SectionOutOfBounds(section as i32)) + } + } + + /// Fills the chunk with the specified block. + /// + /// # Arguments + /// + /// * `block` - The block data to fill the chunk with. + /// + /// # Returns + /// + /// * `Ok(())` - If the chunk was successfully filled. + /// * `Err(WorldError)` - If an error occurs while filling the chunk. + pub fn fill(&mut self, block: BlockData) -> Result<(), WorldError> { + for section in &mut self.sections { + section.fill(block.clone())?; + } + Ok(()) + } } impl Section { + /// Fills the section with the specified block. + /// + /// # Arguments + /// + /// * `block` - The block data to fill the section with. + /// + /// # Returns + /// + /// * `Ok(())` - If the section was successfully filled. + /// * `Err(WorldError)` - If an error occurs while filling the section. + pub fn fill(&mut self, block: BlockData) -> Result<(), WorldError> { + let block_id = BLOCK2ID + .get(&block) + .ok_or(WorldError::InvalidBlock(block.clone()))?; + self.block_states.palette = vec![VarInt::from(*block_id)]; + self.block_states.data = vec![0; 256]; + self.block_states.block_counts = HashMap::from([(block.clone(), 4096)]); + if ["minecraft:air", "minecraft:void_air", "minecraft:cave_air"] + .contains(&block.name.as_str()) + { + self.block_states.non_air_blocks = 0; + } else { + self.block_states.non_air_blocks = 4096; + } + self.block_states.bits_per_block = 4; + Ok(()) + } + /// This function trims out unnecessary data from the section. Primarily it does 2 things: /// /// 1. Removes any palette entries that are not used in the block states data. diff --git a/src/lib/world/src/db_functions.rs b/src/lib/world/src/db_functions.rs index efa69ab3..2d852aac 100644 --- a/src/lib/world/src/db_functions.rs +++ b/src/lib/world/src/db_functions.rs @@ -121,6 +121,12 @@ impl World { } pub(crate) async fn save_chunk_internal(world: &World, chunk: Chunk) -> Result<(), WorldError> { + if !world.storage_backend.table_exists("chunks".to_string()).await? { + world + .storage_backend + .create_table("chunks".to_string()) + .await?; + } let as_bytes = world.compressor.compress(&bitcode::encode(&chunk))?; let digest = create_key(chunk.dimension.as_str(), chunk.x, chunk.z); world @@ -209,6 +215,9 @@ pub(crate) async fn chunk_exists_internal( z: i32, dimension: &str, ) -> Result { + if !world.storage_backend.table_exists("chunks".to_string()).await? { + return Ok(false); + } let digest = create_key(dimension, x, z); Ok(world .storage_backend From 2b1082c035dbeec4871ed295106f70281adc514d Mon Sep 17 00:00:00 2001 From: ReCore Date: Thu, 30 Jan 2025 17:49:39 +1030 Subject: [PATCH 18/21] Clippy + fmt --- src/bin/src/systems/chunk_fetcher.rs | 6 ++++-- src/lib/net/src/packets/incoming/player_action.rs | 13 ++++--------- src/lib/storage/src/lmdb.rs | 2 +- src/lib/world/src/db_functions.rs | 12 ++++++++++-- 4 files changed, 19 insertions(+), 14 deletions(-) diff --git a/src/bin/src/systems/chunk_fetcher.rs b/src/bin/src/systems/chunk_fetcher.rs index 1f98b011..0780e554 100644 --- a/src/bin/src/systems/chunk_fetcher.rs +++ b/src/bin/src/systems/chunk_fetcher.rs @@ -4,7 +4,6 @@ use async_trait::async_trait; use ferrumc_core::chunks::chunk_receiver::ChunkReceiver; use ferrumc_state::GlobalState; use ferrumc_world::chunk_format::Chunk; -use ferrumc_world::errors::WorldError; use ferrumc_world::vanilla_chunk_format::BlockData; use std::collections::{BTreeMap, HashMap}; use std::sync::atomic::AtomicBool; @@ -67,7 +66,10 @@ impl System for ChunkFetcher { section, BlockData { name: "minecraft:grass_block".to_string(), - properties: Some(BTreeMap::from([("snowy".to_string(), "false".to_string())])), + properties: Some(BTreeMap::from([( + "snowy".to_string(), + "false".to_string(), + )])), }, )?; } diff --git a/src/lib/net/src/packets/incoming/player_action.rs b/src/lib/net/src/packets/incoming/player_action.rs index 640f5714..e4d9cd68 100644 --- a/src/lib/net/src/packets/incoming/player_action.rs +++ b/src/lib/net/src/packets/incoming/player_action.rs @@ -8,9 +8,9 @@ use ferrumc_net_codec::encode::NetEncodeOpts; use ferrumc_net_codec::net_types::network_position::NetworkPosition; use ferrumc_net_codec::net_types::var_int::VarInt; use ferrumc_state::ServerState; +use ferrumc_world::vanilla_chunk_format::BlockData; use std::sync::Arc; use tracing::debug; -use ferrumc_world::vanilla_chunk_format::BlockData; #[derive(NetDecode)] #[packet(packet_id = "player_action", state = "play")] @@ -29,20 +29,15 @@ impl IncomingPacket for PlayerAction { let mut chunk = state .clone() .world - .load_chunk( - self.location.x >> 4, - self.location.z >> 4, - "overworld", - ).await?; + .load_chunk(self.location.x >> 4, self.location.z >> 4, "overworld") + .await?; chunk.set_block( self.location.x, self.location.y as i32, self.location.z, BlockData::default(), )?; - state.world.save_chunk( - chunk - ).await?; + state.world.save_chunk(chunk).await?; { let packet = BlockChangeAck { sequence: self.sequence, diff --git a/src/lib/storage/src/lmdb.rs b/src/lib/storage/src/lmdb.rs index af7361d5..4549b8bb 100644 --- a/src/lib/storage/src/lmdb.rs +++ b/src/lib/storage/src/lmdb.rs @@ -210,7 +210,7 @@ impl LmdbBackend { .await .expect("Failed to run tokio task") } - + pub async fn table_exists(&self, table: String) -> Result { let env = self.env.clone(); tokio::task::spawn_blocking(move || { diff --git a/src/lib/world/src/db_functions.rs b/src/lib/world/src/db_functions.rs index 2d852aac..0ec71098 100644 --- a/src/lib/world/src/db_functions.rs +++ b/src/lib/world/src/db_functions.rs @@ -121,7 +121,11 @@ impl World { } pub(crate) async fn save_chunk_internal(world: &World, chunk: Chunk) -> Result<(), WorldError> { - if !world.storage_backend.table_exists("chunks".to_string()).await? { + if !world + .storage_backend + .table_exists("chunks".to_string()) + .await? + { world .storage_backend .create_table("chunks".to_string()) @@ -215,7 +219,11 @@ pub(crate) async fn chunk_exists_internal( z: i32, dimension: &str, ) -> Result { - if !world.storage_backend.table_exists("chunks".to_string()).await? { + if !world + .storage_backend + .table_exists("chunks".to_string()) + .await? + { return Ok(false); } let digest = create_key(dimension, x, z); From 2f4aa8af281cb5f4f04e9285e780eeaa89ea03d1 Mon Sep 17 00:00:00 2001 From: ReCore Date: Sun, 2 Feb 2025 16:05:54 +1030 Subject: [PATCH 19/21] Switched the chunk format to an enum for different palette types --- src/bin/src/systems/chunk_fetcher.rs | 2 +- src/bin/src/systems/chunk_sender.rs | 2 +- src/lib/core/src/chunks/chunk_receiver.rs | 5 +- .../net/src/packets/incoming/player_action.rs | 4 + .../packets/outgoing/chunk_and_light_data.rs | 71 +-- src/lib/world/src/chunk_format.rs | 537 ++++++++++-------- 6 files changed, 337 insertions(+), 284 deletions(-) diff --git a/src/bin/src/systems/chunk_fetcher.rs b/src/bin/src/systems/chunk_fetcher.rs index 0780e554..98275b3e 100644 --- a/src/bin/src/systems/chunk_fetcher.rs +++ b/src/bin/src/systems/chunk_fetcher.rs @@ -104,7 +104,7 @@ impl System for ChunkFetcher { } } } - tokio::time::sleep(std::time::Duration::from_millis(5)).await; + tokio::time::sleep(std::time::Duration::from_millis(45)).await; } } diff --git a/src/bin/src/systems/chunk_sender.rs b/src/bin/src/systems/chunk_sender.rs index ecf41a35..1fa47d04 100644 --- a/src/bin/src/systems/chunk_sender.rs +++ b/src/bin/src/systems/chunk_sender.rs @@ -155,7 +155,7 @@ impl System for ChunkSenderSystem { } } - tokio::time::sleep(Duration::from_millis(5)).await; + tokio::time::sleep(Duration::from_millis(45)).await; } } diff --git a/src/lib/core/src/chunks/chunk_receiver.rs b/src/lib/core/src/chunks/chunk_receiver.rs index b796620f..d44ef498 100644 --- a/src/lib/core/src/chunks/chunk_receiver.rs +++ b/src/lib/core/src/chunks/chunk_receiver.rs @@ -33,7 +33,10 @@ impl ChunkReceiver { pub fn queue_chunk_resend(&mut self, x: i32, z: i32, dimension: String) { if self.can_see.contains(&(x, z, dimension.clone())) { - self.needed_chunks.insert((x, z, dimension), None); + let entry = self.needed_chunks.get_mut(&(x, z, dimension.clone())); + if let Some(entry) = entry { + *entry = None; + } } } } diff --git a/src/lib/net/src/packets/incoming/player_action.rs b/src/lib/net/src/packets/incoming/player_action.rs index e4d9cd68..367ee9b9 100644 --- a/src/lib/net/src/packets/incoming/player_action.rs +++ b/src/lib/net/src/packets/incoming/player_action.rs @@ -31,6 +31,9 @@ impl IncomingPacket for PlayerAction { .world .load_chunk(self.location.x >> 4, self.location.z >> 4, "overworld") .await?; + let block = + chunk.get_block(self.location.x, self.location.y as i32, self.location.z)?; + debug!("Block: {:?}", block); chunk.set_block( self.location.x, self.location.y as i32, @@ -53,6 +56,7 @@ impl IncomingPacket for PlayerAction { { let q = state.universe.query::<&mut ChunkReceiver>(); for (_, mut chunk_receiver) in q { + debug!("Queueing chunk resend"); chunk_receiver.queue_chunk_resend( self.location.x >> 4, self.location.z >> 4, diff --git a/src/lib/net/src/packets/outgoing/chunk_and_light_data.rs b/src/lib/net/src/packets/outgoing/chunk_and_light_data.rs index ace00168..7ca93d21 100644 --- a/src/lib/net/src/packets/outgoing/chunk_and_light_data.rs +++ b/src/lib/net/src/packets/outgoing/chunk_and_light_data.rs @@ -4,10 +4,10 @@ use ferrumc_macros::{packet, NetEncode}; use ferrumc_net_codec::net_types::bitset::BitSet; use ferrumc_net_codec::net_types::length_prefixed_vec::LengthPrefixedVec; use ferrumc_net_codec::net_types::var_int::VarInt; -use ferrumc_world::chunk_format::{Chunk, Heightmaps}; +use ferrumc_world::chunk_format::{Chunk, Heightmaps, PaletteType}; use std::io::{Cursor, Write}; use std::ops::Not; -use tracing::{trace, warn}; +use tracing::{debug, trace, warn}; const SECTIONS: usize = 24; // Number of sections, adjust for your Y range (-64 to 319) @@ -64,7 +64,7 @@ impl ChunkAndLightData { } pub fn from_chunk(chunk: &Chunk) -> Result { - let mut data = Cursor::new(Vec::new()); + let mut raw_data = Cursor::new(Vec::new()); let mut sky_light_data = Vec::new(); let mut block_light_data = Vec::new(); for section in &chunk.sections { @@ -89,49 +89,40 @@ impl ChunkAndLightData { }; block_light_data.push(section_block_light_data); - data.write_u16::(section.block_states.non_air_blocks)?; + raw_data.write_u16::(section.block_states.non_air_blocks)?; - let bits_per_block = section.block_states.bits_per_block; - data.write_u8(bits_per_block)?; - // If bits_per_block is 0, the section is using the single-value palette format - // If bits_per_block is greater than 0, the section is using the indirect palette format - if bits_per_block > 0 { - // Write the palette - VarInt::new(section.block_states.palette.len() as i32).write(&mut data)?; - for palette_entry in §ion.block_states.palette { - palette_entry.write(&mut data)?; + match §ion.block_states.block_data { + PaletteType::Single(val) => { + debug!("Single palette type: {:?}", (chunk.x, chunk.z)); + raw_data.write_u8(0)?; + val.write(&mut raw_data)?; + VarInt::new(0).write(&mut raw_data)?; } - - // Write the data - VarInt::new(section.block_states.data.len() as i32).write(&mut data)?; - for data_entry in §ion.block_states.data { - data.write_i64::(*data_entry)?; - } - } else { - // The 0s for air blocks and bits_per_block are already written - // Get the only palette entry - match section.block_states.palette.first() { - Some(palette_entry) => { - palette_entry.write(&mut data)?; + PaletteType::Indirect { + bits_per_block, + data, + palette, + } => { + debug!("Indirect palette type: {:?}", (chunk.x, chunk.z)); + raw_data.write_u8(*bits_per_block)?; + VarInt::new(palette.len() as i32).write(&mut raw_data)?; + for palette_entry in palette { + palette_entry.write(&mut raw_data)?; } - // If there is no palette entry, write a 0 (air) and log a warning - None => { - VarInt::new(0).write(&mut data)?; - trace!( - "No palette entry found for section at {}, {}, {}", - chunk.x, - section.y, - chunk.z - ); + VarInt::new(data.len() as i32).write(&mut raw_data)?; + for data_entry in data { + raw_data.write_i64::(*data_entry)?; } } - // Write the empty data section's length (0) - VarInt::new(0).write(&mut data)?; + PaletteType::Direct { .. } => { + todo!("Direct palette type") + } } + // Empty biome data for now - data.write_u8(0)?; - data.write_u8(0)?; - data.write_u8(0)?; + raw_data.write_u8(0)?; + raw_data.write_u8(0)?; + raw_data.write_u8(0)?; } let mut sky_light_mask = BitSet::new(SECTIONS + 2); let mut block_light_mask = BitSet::new(SECTIONS + 2); @@ -168,7 +159,7 @@ impl ChunkAndLightData { chunk_x: chunk.x, chunk_z: chunk.z, heightmaps: chunk.heightmaps.serialize_as_network(), - data: LengthPrefixedVec::new(data.into_inner()), + data: LengthPrefixedVec::new(raw_data.into_inner()), block_entities: LengthPrefixedVec::new(Vec::new()), sky_light_mask, block_light_mask, diff --git a/src/lib/world/src/chunk_format.rs b/src/lib/world/src/chunk_format.rs index 51bfbe89..ea0e6830 100644 --- a/src/lib/world/src/chunk_format.rs +++ b/src/lib/world/src/chunk_format.rs @@ -68,13 +68,25 @@ pub struct Section { } #[derive(Encode, Decode, Clone, DeepSizeOf)] pub struct BlockStates { - pub bits_per_block: u8, pub non_air_blocks: u16, - pub data: Vec, - pub palette: Vec, + pub block_data: PaletteType, pub block_counts: HashMap, } +#[derive(Encode, Decode, Clone, DeepSizeOf)] +pub enum PaletteType { + Single(VarInt), + Indirect { + bits_per_block: u8, + data: Vec, + palette: Vec, + }, + Direct { + bits_per_block: u8, + data: Vec, + }, +} + #[derive(Encode, Decode, Clone, DeepSizeOf)] pub struct BiomeStates { pub bits_per_biome: u8, @@ -115,7 +127,8 @@ impl VanillaChunk { let mut sections = Vec::new(); for section in self.sections.as_ref().unwrap() { let y = section.y; - let block_data = section + let mut block_data: PaletteType; + let raw_block_data = section .block_states .as_ref() .and_then(|bs| bs.data.clone()) @@ -127,7 +140,7 @@ impl VanillaChunk { .unwrap_or_default(); let bits_per_block = max((palette.len() as f32).log2().ceil() as u8, 4); let mut block_counts = HashMap::new(); - for chunk in &block_data { + for chunk in &raw_block_data { let mut i = 0; while i + bits_per_block < 64 { let palette_index = read_nbit_i32(chunk, bits_per_block as usize, i as u32)?; @@ -142,13 +155,16 @@ impl VanillaChunk { i += bits_per_block; } } - if block_data.is_empty() { - let single_block = if let Some(block) = palette.first() { - block - } else { - &BlockData::default() - }; + if raw_block_data.is_empty() { + let single_block = BlockData::default(); + block_data = PaletteType::Single(VarInt::from(0)); block_counts.insert(single_block.clone(), 4096); + } else { + block_data = PaletteType::Indirect { + bits_per_block, + data: raw_block_data, + palette: convert_to_net_palette(palette)?, + }; } // Count the number of blocks that are either air, void air, or cave air let mut air_blocks = *block_counts.get(&BlockData::default()).unwrap_or(&0) as u16; @@ -166,11 +182,9 @@ impl VanillaChunk { .unwrap_or(&0) as u16; let non_air_blocks = 4096 - air_blocks; let block_states = BlockStates { - bits_per_block, block_counts, non_air_blocks, - data: block_data, - palette: convert_to_net_palette(palette)?, + block_data, }; let block_light = section .block_light @@ -226,93 +240,112 @@ impl VanillaChunk { impl BlockStates { pub fn resize(&mut self, new_bit_size: usize) -> Result<(), WorldError> { - debug!( - "Resizing block states from {} to {} bits per block", - self.bits_per_block, new_bit_size - ); - let max_int_value = (1 << new_bit_size) - 1; - - if self.data.is_empty() { - let data_size = (4096 * new_bit_size).div_ceil(64); - self.data = vec![0; data_size]; - self.bits_per_block = new_bit_size as u8; - return Ok(()); - } + match &mut self.block_data { + PaletteType::Single(val) => { + let block = ID2BLOCK + .get(&val.val) + .cloned() + .unwrap_or(BlockData::default()); + let mut new_palette = vec![VarInt::from(0); 1]; + if let Some(id) = BLOCK2ID.get(&block) { + new_palette[0] = VarInt::from(*id); + } else { + error!("Could not find block id for palette entry: {:?}", block); + } + self.block_data = PaletteType::Indirect { + bits_per_block: new_bit_size as u8, + data: vec![], + palette: new_palette, + } + } + PaletteType::Indirect { + bits_per_block, + data, + palette, + } => { + // Step 1: Read existing packed data into a list of normal integers + let mut normalised_ints = Vec::with_capacity(4096); + let mut values_read = 0; + + for long in data { + let mut bit_offset = 0; + + while bit_offset + *bits_per_block as usize <= 64 { + if values_read >= 4096 { + break; + } - // Step 1: Read existing packed data into a list of normal integers - let mut normalised_ints = Vec::with_capacity(4096); - let mut values_read = 0; + // Extract value at the current bit offset + let value = + read_nbit_i32(&long, *bits_per_block as usize, bit_offset as u32)?; + let max_int_value = (1 << new_bit_size) - 1; + if value > max_int_value { + return Err(InvalidBlockStateData(format!( + "Value {} exceeds maximum value for {}-bit block state", + value, new_bit_size + ))); + } + normalised_ints.push(value); + values_read += 1; - for &long in &self.data { - let mut bit_offset = 0; + bit_offset += *bits_per_block as usize; + } - while bit_offset + self.bits_per_block as usize <= 64 { - if values_read >= 4096 { - break; + // Stop reading if we’ve already hit 4096 values + if values_read >= 4096 { + break; + } } - // Extract value at the current bit offset - let value = read_nbit_i32(&long, self.bits_per_block as usize, bit_offset as u32)?; - if value > max_int_value { + // Check if we read exactly 4096 block states + if normalised_ints.len() != 4096 { return Err(InvalidBlockStateData(format!( - "Value {} exceeds maximum value for {}-bit block state", - value, new_bit_size + "Expected 4096 block states, but got {}", + normalised_ints.len() ))); } - normalised_ints.push(value); - values_read += 1; - - bit_offset += self.bits_per_block as usize; - } - // Stop reading if we’ve already hit 4096 values - if values_read >= 4096 { - break; - } - } + // Step 2: Write the normalised integers into the new packed format + let mut new_data = Vec::new(); + let mut current_long: i64 = 0; + let mut bit_position = 0; - // Check if we read exactly 4096 block states - if normalised_ints.len() != 4096 { - return Err(InvalidBlockStateData(format!( - "Expected 4096 block states, but got {}", - normalised_ints.len() - ))); - } + for &value in &normalised_ints { + current_long |= (value as i64) << bit_position; + bit_position += new_bit_size; - // Step 2: Write the normalised integers into the new packed format - let mut new_data = Vec::new(); - let mut current_long: i64 = 0; - let mut bit_position = 0; + if bit_position >= 64 { + new_data.push(current_long); + current_long = (value as i64) >> (new_bit_size - (bit_position - 64)); + bit_position -= 64; + } + } - for &value in &normalised_ints { - current_long |= (value as i64) << bit_position; - bit_position += new_bit_size; + // Push any remaining bits in the final long + if bit_position > 0 { + new_data.push(current_long); + } - if bit_position >= 64 { - new_data.push(current_long); - current_long = (value as i64) >> (new_bit_size - (bit_position - 64)); - bit_position -= 64; + // Verify the size of the new data matches expectations + let expected_size = (4096 * new_bit_size).div_ceil(64); + if new_data.len() != expected_size { + return Err(InvalidBlockStateData(format!( + "Expected packed data size of {}, but got {}", + expected_size, + new_data.len() + ))); + } + // Update the chunk with the new packed data and bit size + self.block_data = PaletteType::Indirect { + bits_per_block: new_bit_size as u8, + data: new_data, + palette: palette.clone(), + } } - } - - // Push any remaining bits in the final long - if bit_position > 0 { - new_data.push(current_long); - } - - // Verify the size of the new data matches expectations - let expected_size = (4096 * new_bit_size).div_ceil(64); - if new_data.len() != expected_size { - return Err(InvalidBlockStateData(format!( - "Expected packed data size of {}, but got {}", - expected_size, - new_data.len() - ))); - } - // Update the chunk with the new packed data and bit size - self.data = new_data; - self.bits_per_block = new_bit_size as u8; - + _ => { + todo!("Implement resizing for direct palette") + } + }; Ok(()) } } @@ -358,72 +391,82 @@ impl Chunk { .iter_mut() .find(|section| section.y == (y >> 4) as i8) .ok_or(WorldError::SectionOutOfBounds(y >> 4))?; - // Since we've already checked if the blocks are the same, if the palette is in single block - // mode, we need to convert to palette'd mode - if section.block_states.palette.len() == 1 || section.block_states.bits_per_block == 0 { - section.block_states.resize(4)?; - } - let bits_per_block = section.block_states.bits_per_block; - let block_counts = &mut section.block_states.block_counts; - match block_counts.get_mut(&old_block) { - Some(e) => { - if *e <= 0 { - return Err(WorldError::InvalidBlock(old_block)); + match &mut section.block_states.block_data { + PaletteType::Single(val) => { + debug!("Converting single block to indirect palette"); + // If it's a single block, convert it to indirect then re-run the function + section.block_states.block_data = PaletteType::Indirect { + bits_per_block: 4, + data: vec![0; 256], + palette: vec![val.clone()], + }; + self.set_block(x, y, z, block)?; + } + PaletteType::Indirect { + bits_per_block, + data, + palette, + } => { + let block_counts = &mut section.block_states.block_counts; + match block_counts.get_mut(&old_block) { + Some(e) => { + if *e <= 0 { + return Err(WorldError::InvalidBlock(old_block)); + } + *e -= 1; + } + None => { + warn!("Block not found in block counts: {:?}", old_block); + } + } + let block_id = BLOCK2ID + .get(&block) + .ok_or(WorldError::InvalidBlock(block.clone()))?; + // Add new block + if let Some(e) = section.block_states.block_counts.get(&block) { + section.block_states.block_counts.insert(block, e + 1); + } else { + debug!("Adding block to block counts"); + section.block_states.block_counts.insert(block, 1); + } + // Get block index + let block_palette_index = palette + .iter() + .position(|p| p.val == *block_id) + .unwrap_or_else(|| { + // Add block to palette if it doesn't exist + let index = palette.len() as i16; + palette.push((*block_id).into()); + index as usize + }); + // Set block + let blocks_per_i64 = (64f64 / *bits_per_block as f64).floor() as usize; + let index = ((y & 0xf) * 256 + (z & 0xf) * 16 + (x & 0xf)) as usize; + let i64_index = index / blocks_per_i64; + let packed_u64 = data + .get_mut(i64_index) + .ok_or(InvalidBlockStateData(format!( + "Invalid block state data at index {}", + i64_index + )))?; + let offset = (index % blocks_per_i64) * *bits_per_block as usize; + if let Err(e) = ferrumc_general_purpose::data_packing::u32::write_nbit_u32( + packed_u64, + offset as u32, + block_palette_index as u32, + *bits_per_block, + ) { + return Err(InvalidBlockStateData(format!( + "Failed to write block: {}", + e + ))); } - *e -= 1; } - None => { - warn!("Block not found in block counts: {:?}", old_block); + PaletteType::Direct { .. } => { + todo!("Implement direct palette for set_block"); } } - let block_id = BLOCK2ID - .get(&block) - .ok_or(WorldError::InvalidBlock(block.clone()))?; - // Add new block - if let Some(e) = section.block_states.block_counts.get(&block) { - section.block_states.block_counts.insert(block, e + 1); - } else { - debug!("Adding block to block counts"); - section.block_states.block_counts.insert(block, 1); - } - // Get block index - let block_palette_index = section - .block_states - .palette - .iter() - .position(|p| p.val == *block_id) - .unwrap_or_else(|| { - // Add block to palette if it doesn't exist - let index = section.block_states.palette.len() as i16; - section.block_states.palette.push((*block_id).into()); - index as usize - }); - // Set block - let blocks_per_i64 = (64f64 / bits_per_block as f64).floor() as usize; - let index = ((y & 0xf) * 256 + (z & 0xf) * 16 + (x & 0xf)) as usize; - let i64_index = index / blocks_per_i64; - let packed_u64 = - section - .block_states - .data - .get_mut(i64_index) - .ok_or(InvalidBlockStateData(format!( - "Invalid block state data at index {}", - i64_index - )))?; - let offset = (index % blocks_per_i64) * bits_per_block as usize; - if let Err(e) = ferrumc_general_purpose::data_packing::u32::write_nbit_u32( - packed_u64, - offset as u32, - block_palette_index as u32, - bits_per_block, - ) { - return Err(InvalidBlockStateData(format!( - "Failed to write block: {}", - e - ))); - } - section.block_states.bits_per_block = bits_per_block; + Ok(()) } @@ -450,36 +493,46 @@ impl Chunk { .iter() .find(|section| section.y == (y >> 4) as i8) .ok_or(WorldError::SectionOutOfBounds(y >> 4))?; - let bits_per_block = section.block_states.bits_per_block as usize; - if section.block_states.palette.len() == 1 || bits_per_block == 0 { - return ID2BLOCK - .get(§ion.block_states.palette[0].val) - .cloned() - .ok_or(WorldError::ChunkNotFound); + match §ion.block_states.block_data { + PaletteType::Single(val) => { + let block_id = val.val; + ID2BLOCK + .get(&block_id) + .cloned() + .ok_or(WorldError::ChunkNotFound) + } + PaletteType::Indirect { + bits_per_block, + data, + palette, + } => { + if palette.len() == 1 || *bits_per_block == 0 { + return ID2BLOCK + .get(&palette[0].val) + .cloned() + .ok_or(WorldError::ChunkNotFound); + } + let blocks_per_i64 = (64f64 / *bits_per_block as f64).floor() as usize; + let index = ((y & 0xf) * 256 + (z & 0xf) * 16 + (x & 0xf)) as usize; + let i64_index = index / blocks_per_i64; + let packed_u64 = data.get(i64_index).ok_or(InvalidBlockStateData(format!( + "Invalid block state data at index {}", + i64_index + )))?; + let offset = (index % blocks_per_i64) * *bits_per_block as usize; + let id = ferrumc_general_purpose::data_packing::u32::read_nbit_u32( + packed_u64, + *bits_per_block, + offset as u32, + )?; + let palette_id = palette.get(id as usize).ok_or(WorldError::ChunkNotFound)?; + Ok(crate::chunk_format::ID2BLOCK + .get(&palette_id.val) + .unwrap_or(&BlockData::default()) + .clone()) + } + &PaletteType::Direct { .. } => todo!("Implement direct palette for get_block"), } - let data = §ion.block_states.data; - let blocks_per_i64 = (64f64 / bits_per_block as f64).floor() as usize; - let index = ((y & 0xf) * 256 + (z & 0xf) * 16 + (x & 0xf)) as usize; - let i64_index = index / blocks_per_i64; - let packed_u64 = data.get(i64_index).ok_or(InvalidBlockStateData(format!( - "Invalid block state data at index {}", - i64_index - )))?; - let offset = (index % blocks_per_i64) * bits_per_block; - let id = ferrumc_general_purpose::data_packing::u32::read_nbit_u32( - packed_u64, - bits_per_block as u8, - offset as u32, - )?; - let palette_id = section - .block_states - .palette - .get(id as usize) - .ok_or(WorldError::ChunkNotFound)?; - Ok(crate::chunk_format::ID2BLOCK - .get(&palette_id.val) - .unwrap_or(&BlockData::default()) - .clone()) } pub fn new(x: i32, z: i32, dimension: String) -> Self { @@ -487,14 +540,12 @@ impl Chunk { .map(|y| Section { y: y as i8, block_states: BlockStates { - bits_per_block: 4, non_air_blocks: 0, - data: vec![0; 256], - palette: vec![VarInt::from(0)], + block_data: PaletteType::Single(VarInt::from(0)), block_counts: HashMap::from([(BlockData::default(), 4096)]), }, biome_states: BiomeStates { - bits_per_biome: 4, + bits_per_biome: 0, data: vec![], palette: vec![VarInt::from(0)], }, @@ -502,10 +553,9 @@ impl Chunk { sky_light: vec![255; 2048], }) .collect(); - for section in &mut sections { - section.optimise().expect("Failed to optimise section"); - } - debug!(seclen = sections.len()); + // for section in &mut sections { + // section.optimise().expect("Failed to optimise section"); + // } Chunk { x, z, @@ -568,8 +618,7 @@ impl Section { let block_id = BLOCK2ID .get(&block) .ok_or(WorldError::InvalidBlock(block.clone()))?; - self.block_states.palette = vec![VarInt::from(*block_id)]; - self.block_states.data = vec![0; 256]; + self.block_states.block_data = PaletteType::Single(VarInt::from(*block_id)); self.block_states.block_counts = HashMap::from([(block.clone(), 4096)]); if ["minecraft:air", "minecraft:void_air", "minecraft:cave_air"] .contains(&block.name.as_str()) @@ -578,7 +627,6 @@ impl Section { } else { self.block_states.non_air_blocks = 4096; } - self.block_states.bits_per_block = 4; Ok(()) } @@ -588,65 +636,72 @@ impl Section { /// /// 2. If there is only one block in the palette, it converts the palette to single block mode. pub fn optimise(&mut self) -> Result<(), WorldError> { - { - // Remove empty blocks from palette - let mut remove_indexes = Vec::new(); - for (block, count) in &self.block_states.block_counts { - if *count <= 0 { - let block_id = BLOCK2ID - .get(block) - .ok_or(WorldError::InvalidBlock(block.clone()))?; - let index = self - .block_states - .palette - .iter() - .position(|p| p.val == *block_id); - if let Some(index) = index { - remove_indexes.push(index); - } else { - return Err(WorldError::InvalidBlock(block.clone())); + match &mut self.block_states.block_data { + PaletteType::Single(_) => { + // If the section is already in single block mode, there's nothing to optimise + return Ok(()); + } + PaletteType::Indirect { + bits_per_block, + data, + palette, + } => { + // Remove empty blocks from palette + let mut remove_indexes = Vec::new(); + for (block, count) in &self.block_states.block_counts { + if *count <= 0 { + let block_id = BLOCK2ID + .get(block) + .ok_or(WorldError::InvalidBlock(block.clone()))?; + let index = palette.iter().position(|p| p.val == *block_id); + if let Some(index) = index { + remove_indexes.push(index); + } else { + return Err(WorldError::InvalidBlock(block.clone())); + } } } - } - for index in remove_indexes { - // Decrement any data entries that are higher than the removed index - for data in &mut self.block_states.data { - let mut i = 0; - while (i + self.block_states.bits_per_block as usize) < 64 { - let block_index = - ferrumc_general_purpose::data_packing::u32::read_nbit_u32( - data, - self.block_states.bits_per_block, - i as u32, - )?; - if block_index > index as u32 { - ferrumc_general_purpose::data_packing::u32::write_nbit_u32( - data, - i as u32, - block_index - 1, - self.block_states.bits_per_block, - )?; + for index in remove_indexes { + // Decrement any data entries that are higher than the removed index + for data_point in &mut *data { + let mut i = 0; + while (i + *bits_per_block as usize) < 64 { + let block_index = + ferrumc_general_purpose::data_packing::u32::read_nbit_u32( + data_point, + *bits_per_block, + i as u32, + )?; + if block_index > index as u32 { + ferrumc_general_purpose::data_packing::u32::write_nbit_u32( + data_point, + i as u32, + block_index - 1, + *bits_per_block, + )?; + } + i += *bits_per_block as usize; } - i += self.block_states.bits_per_block as usize; + } + } + + { + // If there is only one block in the palette, convert to single block mode + if palette.len() == 1 { + let block = ID2BLOCK + .get(&palette[0].val) + .cloned() + .unwrap_or(BlockData::default()); + self.block_states.block_data = PaletteType::Single(palette[0].clone()); + self.block_states.block_counts.clear(); + self.block_states.block_counts.insert(block, 4096); } } } - } - { - // If there is only one block in the palette, remove the palette and set the block to the first entry - if self.block_states.palette.len() == 1 { - let block_id = self.block_states.palette[0].val; - let block = ID2BLOCK - .get(&block_id) - .cloned() - .unwrap_or(BlockData::default()); - self.block_states.palette.clear(); - self.block_states.palette.push(VarInt::from(block_id)); - self.block_states.data.clear(); - self.block_states.block_counts.clear(); - self.block_states.block_counts.insert(block, 4096); + PaletteType::Direct { .. } => { + todo!("Implement optimisation for direct palette"); } - } + }; Ok(()) } From e78f426da536e5330b02bb5ff26d578bdf098240 Mon Sep 17 00:00:00 2001 From: ReCore Date: Sun, 2 Feb 2025 17:23:57 +1030 Subject: [PATCH 20/21] wtf --- src/bin/src/systems/chunk_fetcher.rs | 9 +++-- src/bin/src/systems/chunk_sender.rs | 22 ++++------- src/lib/core/src/chunks/chunk_receiver.rs | 13 +++++-- .../net/crates/codec/src/net_types/var_int.rs | 2 +- .../packets/outgoing/chunk_and_light_data.rs | 7 ++-- src/lib/world/src/chunk_format.rs | 39 ++++++++++++------- 6 files changed, 52 insertions(+), 40 deletions(-) diff --git a/src/bin/src/systems/chunk_fetcher.rs b/src/bin/src/systems/chunk_fetcher.rs index 98275b3e..b2d85f53 100644 --- a/src/bin/src/systems/chunk_fetcher.rs +++ b/src/bin/src/systems/chunk_fetcher.rs @@ -2,6 +2,7 @@ use crate::errors::BinaryError; use crate::systems::definition::System; use async_trait::async_trait; use ferrumc_core::chunks::chunk_receiver::ChunkReceiver; +use ferrumc_core::chunks::chunk_receiver::ChunkSendState::{Fetching, Sending}; use ferrumc_state::GlobalState; use ferrumc_world::chunk_format::Chunk; use ferrumc_world::vanilla_chunk_format::BlockData; @@ -43,7 +44,7 @@ impl System for ChunkFetcher { let mut copied_chunks = HashMap::new(); for chunk in chunk_recv.needed_chunks.iter() { let (key, chunk) = chunk; - if chunk.is_none() { + if let Fetching = chunk { copied_chunks.insert(key.clone(), None); } } @@ -85,8 +86,10 @@ impl System for ChunkFetcher { trace!("A player disconnected before we could get the ChunkReceiver"); return Ok(()); }; - for (key, chunk) in copied_chunks.iter() { - chunk_recv.needed_chunks.insert(key.clone(), chunk.clone()); + for (key, chunk) in copied_chunks { + if let Some(chunk) = chunk { + chunk_recv.needed_chunks.insert(key.clone(), Sending(chunk)); + } } } Ok(()) diff --git a/src/bin/src/systems/chunk_sender.rs b/src/bin/src/systems/chunk_sender.rs index 1fa47d04..58686e8a 100644 --- a/src/bin/src/systems/chunk_sender.rs +++ b/src/bin/src/systems/chunk_sender.rs @@ -1,6 +1,7 @@ use crate::systems::definition::System; use async_trait::async_trait; use ferrumc_core::chunks::chunk_receiver::ChunkReceiver; +use ferrumc_core::chunks::chunk_receiver::ChunkSendState::{Sending, Sent}; use ferrumc_ecs::errors::ECSError; use ferrumc_net::connection::StreamWriter; use ferrumc_net::packets::outgoing::chunk_and_light_data::ChunkAndLightData; @@ -14,7 +15,7 @@ use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; use std::time::Duration; use tokio::task::JoinSet; -use tracing::{error, info, trace}; +use tracing::{debug, error, info, trace}; pub(super) struct ChunkSenderSystem { pub stop: AtomicBool, @@ -55,7 +56,6 @@ impl System for ChunkSenderSystem { } // We can't delete from the map while iterating, so we collect the keys to drop // and then drop them after sending the chunks - let mut to_drop = Vec::new(); { let Ok(chunk_recv) = state.universe.get::(eid) else { trace!("A player disconnected before we could get the ChunkReceiver"); @@ -75,28 +75,20 @@ impl System for ChunkSenderSystem { .expect("ChunkReceiver not found"); trace!("Got chunk_recv 3 for sender"); for (key, chunk) in chunk_recv.needed_chunks.iter_mut() { - if let Some(chunk) = chunk { - to_drop.push(key.clone()); - match ChunkAndLightData::from_chunk(&chunk.clone()) { + if let Sending(confirmed_chunk) = chunk { + match ChunkAndLightData::from_chunk(&confirmed_chunk.clone()) { Ok(packet) => { + debug!("Queuing chunk for sending"); packets.push(packet); } Err(e) => { error!("Error sending chunk: {:?}", e); } } + *chunk = Sent; } } - } - { - let Ok(mut chunk_recv) = state.universe.get_mut::(eid) - else { - trace!("A player disconnected before we could get the ChunkReceiver"); - return Ok(()); - }; - for key in to_drop { - chunk_recv.needed_chunks.remove(&key); - } + chunk_recv.needed_chunks.retain(|_, v| v != &Sent); } { diff --git a/src/lib/core/src/chunks/chunk_receiver.rs b/src/lib/core/src/chunks/chunk_receiver.rs index d44ef498..9896d867 100644 --- a/src/lib/core/src/chunks/chunk_receiver.rs +++ b/src/lib/core/src/chunks/chunk_receiver.rs @@ -5,7 +5,7 @@ use tokio::time::Instant; const VIEW_DISTANCE: i32 = 8; pub struct ChunkReceiver { - pub needed_chunks: HashMap<(i32, i32, String), Option>, + pub needed_chunks: HashMap<(i32, i32, String), ChunkSendState>, pub can_see: HashSet<(i32, i32, String)>, pub last_update: Instant, pub last_chunk: Option<(i32, i32, String)>, @@ -19,6 +19,13 @@ impl Default for ChunkReceiver { } } +#[derive(Clone, Eq, PartialEq)] +pub enum ChunkSendState { + Fetching, + Sending(Chunk), + Sent, +} + impl ChunkReceiver { pub fn new() -> Self { Self { @@ -35,7 +42,7 @@ impl ChunkReceiver { if self.can_see.contains(&(x, z, dimension.clone())) { let entry = self.needed_chunks.get_mut(&(x, z, dimension.clone())); if let Some(entry) = entry { - *entry = None; + *entry = ChunkSendState::Fetching; } } } @@ -49,7 +56,7 @@ impl ChunkReceiver { for z in last_chunk.1 - VIEW_DISTANCE..=last_chunk.1 + VIEW_DISTANCE { if !self.can_see.contains(&(x, z, last_chunk.2.clone())) { self.needed_chunks - .insert((x, z, last_chunk.2.clone()), None); + .insert((x, z, last_chunk.2.clone()), ChunkSendState::Fetching); } new_can_see.insert((x, z, last_chunk.2.clone())); } diff --git a/src/lib/net/crates/codec/src/net_types/var_int.rs b/src/lib/net/crates/codec/src/net_types/var_int.rs index 33231791..42010c81 100644 --- a/src/lib/net/crates/codec/src/net_types/var_int.rs +++ b/src/lib/net/crates/codec/src/net_types/var_int.rs @@ -8,7 +8,7 @@ use deepsize::DeepSizeOf; use std::io::{Read, Write}; use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; -#[derive(Debug, Encode, Decode, Clone, DeepSizeOf)] +#[derive(Debug, Encode, Decode, Clone, DeepSizeOf, Eq)] pub struct VarInt { /// The value of the VarInt. pub val: i32, diff --git a/src/lib/net/src/packets/outgoing/chunk_and_light_data.rs b/src/lib/net/src/packets/outgoing/chunk_and_light_data.rs index 7ca93d21..8ad4f56f 100644 --- a/src/lib/net/src/packets/outgoing/chunk_and_light_data.rs +++ b/src/lib/net/src/packets/outgoing/chunk_and_light_data.rs @@ -7,7 +7,7 @@ use ferrumc_net_codec::net_types::var_int::VarInt; use ferrumc_world::chunk_format::{Chunk, Heightmaps, PaletteType}; use std::io::{Cursor, Write}; use std::ops::Not; -use tracing::{debug, trace, warn}; +use tracing::{debug, warn}; const SECTIONS: usize = 24; // Number of sections, adjust for your Y range (-64 to 319) @@ -64,6 +64,7 @@ impl ChunkAndLightData { } pub fn from_chunk(chunk: &Chunk) -> Result { + debug!("Serializing chunk at {}, {}", chunk.x, chunk.z); let mut raw_data = Cursor::new(Vec::new()); let mut sky_light_data = Vec::new(); let mut block_light_data = Vec::new(); @@ -93,7 +94,7 @@ impl ChunkAndLightData { match §ion.block_states.block_data { PaletteType::Single(val) => { - debug!("Single palette type: {:?}", (chunk.x, chunk.z)); + // debug!("Single palette type: {:?}", (chunk.x, chunk.z)); raw_data.write_u8(0)?; val.write(&mut raw_data)?; VarInt::new(0).write(&mut raw_data)?; @@ -103,7 +104,7 @@ impl ChunkAndLightData { data, palette, } => { - debug!("Indirect palette type: {:?}", (chunk.x, chunk.z)); + // debug!("Indirect palette type: {:?}", (chunk.x, chunk.z)); raw_data.write_u8(*bits_per_block)?; VarInt::new(palette.len() as i32).write(&mut raw_data)?; for palette_entry in palette { diff --git a/src/lib/world/src/chunk_format.rs b/src/lib/world/src/chunk_format.rs index ea0e6830..5761661b 100644 --- a/src/lib/world/src/chunk_format.rs +++ b/src/lib/world/src/chunk_format.rs @@ -9,6 +9,7 @@ use ferrumc_macros::{NBTDeserialize, NBTSerialize}; use ferrumc_net_codec::net_types::var_int::VarInt; use lazy_static::lazy_static; use std::cmp::max; +use std::collections::hash_map::Entry; use std::collections::HashMap; use std::io::Read; use tracing::{debug, error, warn}; @@ -40,7 +41,7 @@ lazy_static! { ID2BLOCK.iter().map(|(k, v)| (v.clone(), *k)).collect(); } -#[derive(Encode, Decode, Clone, DeepSizeOf)] +#[derive(Encode, Decode, Clone, DeepSizeOf, Eq, PartialEq)] // This is a placeholder for the actual chunk format pub struct Chunk { pub x: i32, @@ -52,13 +53,14 @@ pub struct Chunk { #[derive(Encode, Decode, NBTDeserialize, NBTSerialize, Clone, DeepSizeOf)] #[nbt(net_encode)] +#[derive(Eq, PartialEq)] pub struct Heightmaps { #[nbt(rename = "MOTION_BLOCKING")] pub motion_blocking: Vec, #[nbt(rename = "WORLD_SURFACE")] pub world_surface: Vec, } -#[derive(Encode, Decode, Clone, DeepSizeOf)] +#[derive(Encode, Decode, Clone, DeepSizeOf, Eq, PartialEq)] pub struct Section { pub y: i8, pub block_states: BlockStates, @@ -66,14 +68,14 @@ pub struct Section { pub block_light: Vec, pub sky_light: Vec, } -#[derive(Encode, Decode, Clone, DeepSizeOf)] +#[derive(Encode, Decode, Clone, DeepSizeOf, Eq, PartialEq)] pub struct BlockStates { pub non_air_blocks: u16, pub block_data: PaletteType, pub block_counts: HashMap, } -#[derive(Encode, Decode, Clone, DeepSizeOf)] +#[derive(Encode, Decode, Clone, DeepSizeOf, Eq, PartialEq)] pub enum PaletteType { Single(VarInt), Indirect { @@ -87,7 +89,7 @@ pub enum PaletteType { }, } -#[derive(Encode, Decode, Clone, DeepSizeOf)] +#[derive(Encode, Decode, Clone, DeepSizeOf, Eq, PartialEq)] pub struct BiomeStates { pub bits_per_biome: u8, pub data: Vec, @@ -127,7 +129,7 @@ impl VanillaChunk { let mut sections = Vec::new(); for section in self.sections.as_ref().unwrap() { let y = section.y; - let mut block_data: PaletteType; + let block_data: PaletteType; let raw_block_data = section .block_states .as_ref() @@ -277,7 +279,7 @@ impl BlockStates { // Extract value at the current bit offset let value = - read_nbit_i32(&long, *bits_per_block as usize, bit_offset as u32)?; + read_nbit_i32(long, *bits_per_block as usize, bit_offset as u32)?; let max_int_value = (1 << new_bit_size) - 1; if value > max_int_value { return Err(InvalidBlockStateData(format!( @@ -391,6 +393,7 @@ impl Chunk { .iter_mut() .find(|section| section.y == (y >> 4) as i8) .ok_or(WorldError::SectionOutOfBounds(y >> 4))?; + // Do different things based on the palette type match &mut section.block_states.block_data { PaletteType::Single(val) => { debug!("Converting single block to indirect palette"); @@ -408,15 +411,17 @@ impl Chunk { palette, } => { let block_counts = &mut section.block_states.block_counts; - match block_counts.get_mut(&old_block) { - Some(e) => { - if *e <= 0 { + match block_counts.entry(old_block.clone()) { + Entry::Occupied(mut occ_entry) => { + let count = occ_entry.get_mut(); + if *count <= 0 { return Err(WorldError::InvalidBlock(old_block)); } - *e -= 1; + *count -= 1; } - None => { + Entry::Vacant(empty_entry) => { warn!("Block not found in block counts: {:?}", old_block); + empty_entry.insert(0); } } let block_id = BLOCK2ID @@ -536,12 +541,16 @@ impl Chunk { } pub fn new(x: i32, z: i32, dimension: String) -> Self { - let mut sections: Vec
= (0..24) + let sections: Vec
= (0..24) .map(|y| Section { y: y as i8, block_states: BlockStates { non_air_blocks: 0, - block_data: PaletteType::Single(VarInt::from(0)), + block_data: PaletteType::Indirect { + bits_per_block: 4, + data: vec![0; 256], + palette: vec![VarInt::from(0)], + }, block_counts: HashMap::from([(BlockData::default(), 4096)]), }, biome_states: BiomeStates { @@ -579,7 +588,7 @@ impl Chunk { /// * `Err(WorldError)` - If an error occurs while setting the section. pub fn set_section(&mut self, section: u8, block: BlockData) -> Result<(), WorldError> { if let Some(section) = self.sections.get_mut(section as usize) { - section.fill(block) + section.fill(block.clone()) } else { Err(WorldError::SectionOutOfBounds(section as i32)) } From 6127c07306dba89661b929e119b277cea29a499a Mon Sep 17 00:00:00 2001 From: ReCore Date: Wed, 5 Feb 2025 17:44:22 +1030 Subject: [PATCH 21/21] Now works a little bit more, still broken af --- src/bin/Cargo.toml | 2 +- src/bin/src/systems/chunk_fetcher.rs | 34 ++++++---- src/bin/src/systems/chunk_sender.rs | 5 +- src/lib/core/src/chunks/chunk_receiver.rs | 9 +++ .../net/src/packets/incoming/place_block.rs | 1 + .../net/src/packets/incoming/player_action.rs | 44 +++++++------ .../packets/outgoing/chunk_and_light_data.rs | 3 +- src/lib/world/src/chunk_format.rs | 64 +++++++++++-------- 8 files changed, 97 insertions(+), 65 deletions(-) diff --git a/src/bin/Cargo.toml b/src/bin/Cargo.toml index 1e758287..ec98ad79 100644 --- a/src/bin/Cargo.toml +++ b/src/bin/Cargo.toml @@ -35,7 +35,7 @@ async-trait = { workspace = true } clap = { workspace = true, features = ["derive"] } flate2 = { workspace = true } ctor = { workspace = true } -log = "0.4.22" +rand = { workspace = true } [[bin]] diff --git a/src/bin/src/systems/chunk_fetcher.rs b/src/bin/src/systems/chunk_fetcher.rs index b2d85f53..bb940923 100644 --- a/src/bin/src/systems/chunk_fetcher.rs +++ b/src/bin/src/systems/chunk_fetcher.rs @@ -6,7 +6,7 @@ use ferrumc_core::chunks::chunk_receiver::ChunkSendState::{Fetching, Sending}; use ferrumc_state::GlobalState; use ferrumc_world::chunk_format::Chunk; use ferrumc_world::vanilla_chunk_format::BlockData; -use std::collections::{BTreeMap, HashMap}; +use std::collections::HashMap; use std::sync::atomic::AtomicBool; use std::sync::Arc; use tokio::task::JoinSet; @@ -16,6 +16,22 @@ pub struct ChunkFetcher { stop: AtomicBool, } +fn generate_chunk(x: i32, z: i32) -> Chunk { + let mut new_chunk = Chunk::new(x, z, "overworld".to_string()); + for y in 0..10 { + new_chunk + .set_section( + y, + BlockData { + name: "minecraft:stone".to_string(), + properties: None, + }, + ) + .unwrap() + } + new_chunk +} + impl ChunkFetcher { pub(crate) fn new() -> Self { Self { @@ -61,24 +77,14 @@ impl System for ChunkFetcher { state.world.load_chunk(key.0, key.1, &key.2.clone()).await? } else { debug!("Chunk not found, creating new chunk"); - let mut new_chunk = Chunk::new(key.0, key.1, key.2.clone()); - for section in 0..8 { - new_chunk.set_section( - section, - BlockData { - name: "minecraft:grass_block".to_string(), - properties: Some(BTreeMap::from([( - "snowy".to_string(), - "false".to_string(), - )])), - }, - )?; - } + let new_chunk = generate_chunk(key.0, key.1); + state.world.save_chunk(new_chunk.clone()).await?; new_chunk }; *chunk = Some(fetched_chunk); } + state.world.sync().await?; // Insert the fetched chunks back into the component { let Ok(mut chunk_recv) = state.universe.get_mut::(eid) diff --git a/src/bin/src/systems/chunk_sender.rs b/src/bin/src/systems/chunk_sender.rs index 58686e8a..2b48bc7a 100644 --- a/src/bin/src/systems/chunk_sender.rs +++ b/src/bin/src/systems/chunk_sender.rs @@ -15,7 +15,7 @@ use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; use std::time::Duration; use tokio::task::JoinSet; -use tracing::{debug, error, info, trace}; +use tracing::{error, info, trace}; pub(super) struct ChunkSenderSystem { pub stop: AtomicBool, @@ -74,11 +74,10 @@ impl System for ChunkSenderSystem { .get_mut::(eid) .expect("ChunkReceiver not found"); trace!("Got chunk_recv 3 for sender"); - for (key, chunk) in chunk_recv.needed_chunks.iter_mut() { + for (_key, chunk) in chunk_recv.needed_chunks.iter_mut() { if let Sending(confirmed_chunk) = chunk { match ChunkAndLightData::from_chunk(&confirmed_chunk.clone()) { Ok(packet) => { - debug!("Queuing chunk for sending"); packets.push(packet); } Err(e) => { diff --git a/src/lib/core/src/chunks/chunk_receiver.rs b/src/lib/core/src/chunks/chunk_receiver.rs index 9896d867..33f868ca 100644 --- a/src/lib/core/src/chunks/chunk_receiver.rs +++ b/src/lib/core/src/chunks/chunk_receiver.rs @@ -46,6 +46,15 @@ impl ChunkReceiver { } } } + + pub fn queue_from_chunk(&mut self, chunk: Chunk) { + let key = (chunk.x, chunk.z, chunk.dimension.clone()); + + if self.can_see.contains(&key) { + self.needed_chunks + .insert(key, ChunkSendState::Sending(chunk)); + } + } } impl ChunkReceiver { diff --git a/src/lib/net/src/packets/incoming/place_block.rs b/src/lib/net/src/packets/incoming/place_block.rs index 283edd29..9c1649df 100644 --- a/src/lib/net/src/packets/incoming/place_block.rs +++ b/src/lib/net/src/packets/incoming/place_block.rs @@ -102,6 +102,7 @@ impl IncomingPacket for PlaceBlock { }, )?; state.world.save_chunk(chunk).await?; + state.world.sync().await?; let q = state.universe.query::<&mut ChunkReceiver>(); for (_, mut chunk_recv) in q { chunk_recv.queue_chunk_resend(x >> 4, z >> 4, "overworld".to_string()); diff --git a/src/lib/net/src/packets/incoming/player_action.rs b/src/lib/net/src/packets/incoming/player_action.rs index 367ee9b9..6f063a0b 100644 --- a/src/lib/net/src/packets/incoming/player_action.rs +++ b/src/lib/net/src/packets/incoming/player_action.rs @@ -1,8 +1,8 @@ use crate::connection::StreamWriter; use crate::packets::outgoing::block_change_ack::BlockChangeAck; +use crate::packets::outgoing::chunk_and_light_data::ChunkAndLightData; use crate::packets::IncomingPacket; use crate::NetResult; -use ferrumc_core::chunks::chunk_receiver::ChunkReceiver; use ferrumc_macros::{packet, NetDecode}; use ferrumc_net_codec::encode::NetEncodeOpts; use ferrumc_net_codec::net_types::network_position::NetworkPosition; @@ -34,36 +34,40 @@ impl IncomingPacket for PlayerAction { let block = chunk.get_block(self.location.x, self.location.y as i32, self.location.z)?; debug!("Block: {:?}", block); - chunk.set_block( - self.location.x, + let (relative_x, relative_y, relative_z) = ( + self.location.x & 0xF, self.location.y as i32, - self.location.z, - BlockData::default(), - )?; - state.world.save_chunk(chunk).await?; + self.location.z & 0xF, + ); + chunk.set_block(relative_x, relative_y, relative_z, BlockData::default())?; + // debug!(chunk = ?chunk, "Chunk after block placement"); + state.world.save_chunk(chunk.clone()).await?; + state.world.sync().await?; { - let packet = BlockChangeAck { + let ack_packet = BlockChangeAck { sequence: self.sequence, }; if let Ok(mut conn) = state.universe.get_mut::(conn_id) { - conn.send_packet(packet, &NetEncodeOpts::WithLength)?; + let chunk_packet = ChunkAndLightData::from_chunk(&chunk)?; + conn.send_packet(chunk_packet, &NetEncodeOpts::WithLength)?; + conn.send_packet(ack_packet, &NetEncodeOpts::WithLength)?; } else { debug!( "Player disconnected before we could send the BlockChangeAck packet" ); } } - { - let q = state.universe.query::<&mut ChunkReceiver>(); - for (_, mut chunk_receiver) in q { - debug!("Queueing chunk resend"); - chunk_receiver.queue_chunk_resend( - self.location.x >> 4, - self.location.z >> 4, - "overworld".to_string(), - ); - } - } + // { + // let q = state.universe.query::<&mut ChunkReceiver>(); + // for (_, mut chunk_receiver) in q { + // debug!("Queueing chunk resend"); + // chunk_receiver.queue_chunk_resend( + // self.location.x >> 4, + // self.location.z >> 4, + // "overworld".to_string(), + // ); + // } + // } } 1 => { debug!("You shouldn't be seeing this in creative mode."); diff --git a/src/lib/net/src/packets/outgoing/chunk_and_light_data.rs b/src/lib/net/src/packets/outgoing/chunk_and_light_data.rs index 8ad4f56f..7965366f 100644 --- a/src/lib/net/src/packets/outgoing/chunk_and_light_data.rs +++ b/src/lib/net/src/packets/outgoing/chunk_and_light_data.rs @@ -7,7 +7,7 @@ use ferrumc_net_codec::net_types::var_int::VarInt; use ferrumc_world::chunk_format::{Chunk, Heightmaps, PaletteType}; use std::io::{Cursor, Write}; use std::ops::Not; -use tracing::{debug, warn}; +use tracing::warn; const SECTIONS: usize = 24; // Number of sections, adjust for your Y range (-64 to 319) @@ -64,7 +64,6 @@ impl ChunkAndLightData { } pub fn from_chunk(chunk: &Chunk) -> Result { - debug!("Serializing chunk at {}, {}", chunk.x, chunk.z); let mut raw_data = Cursor::new(Vec::new()); let mut sky_light_data = Vec::new(); let mut block_light_data = Vec::new(); diff --git a/src/lib/world/src/chunk_format.rs b/src/lib/world/src/chunk_format.rs index 5761661b..0b150ddb 100644 --- a/src/lib/world/src/chunk_format.rs +++ b/src/lib/world/src/chunk_format.rs @@ -12,7 +12,7 @@ use std::cmp::max; use std::collections::hash_map::Entry; use std::collections::HashMap; use std::io::Read; -use tracing::{debug, error, warn}; +use tracing::{error, warn}; use vanilla_chunk_format::BlockData; #[cfg(test)] @@ -41,7 +41,7 @@ lazy_static! { ID2BLOCK.iter().map(|(k, v)| (v.clone(), *k)).collect(); } -#[derive(Encode, Decode, Clone, DeepSizeOf, Eq, PartialEq)] +#[derive(Encode, Decode, Clone, DeepSizeOf, Eq, PartialEq, Debug)] // This is a placeholder for the actual chunk format pub struct Chunk { pub x: i32, @@ -51,7 +51,7 @@ pub struct Chunk { pub heightmaps: Heightmaps, } -#[derive(Encode, Decode, NBTDeserialize, NBTSerialize, Clone, DeepSizeOf)] +#[derive(Encode, Decode, NBTDeserialize, NBTSerialize, Clone, DeepSizeOf, Debug)] #[nbt(net_encode)] #[derive(Eq, PartialEq)] pub struct Heightmaps { @@ -60,7 +60,7 @@ pub struct Heightmaps { #[nbt(rename = "WORLD_SURFACE")] pub world_surface: Vec, } -#[derive(Encode, Decode, Clone, DeepSizeOf, Eq, PartialEq)] +#[derive(Encode, Decode, Clone, DeepSizeOf, Eq, PartialEq, Debug)] pub struct Section { pub y: i8, pub block_states: BlockStates, @@ -68,14 +68,14 @@ pub struct Section { pub block_light: Vec, pub sky_light: Vec, } -#[derive(Encode, Decode, Clone, DeepSizeOf, Eq, PartialEq)] +#[derive(Encode, Decode, Clone, DeepSizeOf, Eq, PartialEq, Debug)] pub struct BlockStates { pub non_air_blocks: u16, pub block_data: PaletteType, pub block_counts: HashMap, } -#[derive(Encode, Decode, Clone, DeepSizeOf, Eq, PartialEq)] +#[derive(Encode, Decode, Clone, DeepSizeOf, Eq, PartialEq, Debug)] pub enum PaletteType { Single(VarInt), Indirect { @@ -89,7 +89,7 @@ pub enum PaletteType { }, } -#[derive(Encode, Decode, Clone, DeepSizeOf, Eq, PartialEq)] +#[derive(Encode, Decode, Clone, DeepSizeOf, Eq, PartialEq, Debug)] pub struct BiomeStates { pub bits_per_biome: u8, pub data: Vec, @@ -384,7 +384,7 @@ impl Chunk { // Get old block let old_block = self.get_block(x, y, z)?; if old_block == block { - debug!("Block is the same as the old block"); + // debug!("Block is the same as the old block"); return Ok(()); } // Get section @@ -393,25 +393,39 @@ impl Chunk { .iter_mut() .find(|section| section.y == (y >> 4) as i8) .ok_or(WorldError::SectionOutOfBounds(y >> 4))?; + + let mut converted = false; + let mut new_contents = PaletteType::Indirect { + bits_per_block: 4, + data: vec![], + palette: vec![], + }; + + if let PaletteType::Single(val) = §ion.block_states.block_data { + new_contents = PaletteType::Indirect { + bits_per_block: 4, + data: vec![0; 255], + palette: vec![val.clone()], + }; + converted = true; + } + + if converted { + section.block_states.block_data = new_contents; + } + // Do different things based on the palette type match &mut section.block_states.block_data { - PaletteType::Single(val) => { - debug!("Converting single block to indirect palette"); - // If it's a single block, convert it to indirect then re-run the function - section.block_states.block_data = PaletteType::Indirect { - bits_per_block: 4, - data: vec![0; 256], - palette: vec![val.clone()], - }; - self.set_block(x, y, z, block)?; + PaletteType::Single(_val) => { + panic!("Single palette type should have been converted to indirect palette type"); } PaletteType::Indirect { bits_per_block, data, palette, } => { - let block_counts = &mut section.block_states.block_counts; - match block_counts.entry(old_block.clone()) { + // debug!("Indirect mode"); + match section.block_states.block_counts.entry(old_block.clone()) { Entry::Occupied(mut occ_entry) => { let count = occ_entry.get_mut(); if *count <= 0 { @@ -431,9 +445,13 @@ impl Chunk { if let Some(e) = section.block_states.block_counts.get(&block) { section.block_states.block_counts.insert(block, e + 1); } else { - debug!("Adding block to block counts"); + // debug!("Adding block to block counts"); section.block_states.block_counts.insert(block, 1); } + // let required_bits = max((palette.len() as f32).log2().ceil() as u8, 4); + // if *bits_per_block != required_bits { + // section.block_states.resize(required_bits as usize)?; + // } // Get block index let block_palette_index = palette .iter() @@ -546,11 +564,7 @@ impl Chunk { y: y as i8, block_states: BlockStates { non_air_blocks: 0, - block_data: PaletteType::Indirect { - bits_per_block: 4, - data: vec![0; 256], - palette: vec![VarInt::from(0)], - }, + block_data: PaletteType::Single(VarInt::from(0)), block_counts: HashMap::from([(BlockData::default(), 4096)]), }, biome_states: BiomeStates {