From dc2206c5fbcaaabde208befacff52907ad847063 Mon Sep 17 00:00:00 2001 From: ReCore Date: Mon, 16 Dec 2024 18:29:14 +1030 Subject: [PATCH 1/8] 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 2/8] 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 3/8] 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 4/8] 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 5/8] 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 6/8] 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 7/8] 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 8/8] 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); }