use std::{ path::PathBuf, pin::Pin, sync::{ RwLock, atomic::{AtomicBool, Ordering}, }, }; use bytes::Bytes; use pumpkin_data::{Block, BlockStateId, chunk::ChunkStatus, fluid::Fluid}; use pumpkin_nbt::{compound::NbtCompound, nbt_long_array}; use rustc_hash::FxHashMap; use tokio::sync::Mutex; use crate::{ chunk::{ ChunkEntityData, ChunkReadingError, ChunkSerializingError, format::anvil::{SingleChunkDataSerializer, WORLD_DATA_VERSION}, io::{Dirtiable, file_manager::PathFromLevelFolder}, }, generation::section_coords, level::LevelFolder, tick::{ScheduledTick, scheduler::ChunkTickScheduler}, }; use pumpkin_util::math::position::BlockPos; use pumpkin_util::math::vector2::Vector2; use serde::{Deserialize, Serialize}; use super::{ ChunkData, ChunkHeightmaps, ChunkLight, ChunkParsingError, ChunkSections, palette::{BiomePalette, BlockPalette}, }; pub mod anvil; pub mod linear; pub mod pump; impl SingleChunkDataSerializer for ChunkData { #[inline] fn from_bytes(bytes: &Bytes, pos: Vector2) -> Result { Self::internal_from_bytes(bytes, pos).map_err(ChunkReadingError::ParsingError) } #[inline] fn to_bytes( &self, ) -> Pin> + Send + '_>> { Box::pin(async move { self.internal_to_bytes() }) } #[inline] fn position(&self) -> (i32, i32) { (self.x, self.z) } } impl PathFromLevelFolder for ChunkData { #[inline] fn file_path(folder: &LevelFolder, file_name: &str) -> PathBuf { folder.region_folder.join(file_name) } } impl Dirtiable for ChunkData { #[inline] fn mark_dirty(&self, flag: bool) { self.dirty.store(flag, Ordering::Relaxed); } #[inline] fn is_dirty(&self) -> bool { self.dirty.load(Ordering::Relaxed) } } impl ChunkData { pub fn internal_from_bytes( chunk_data: &[u8], position: Vector2, ) -> Result { let is_named = chunk_data.len() >= 3 && chunk_data[0] == 0x0a && chunk_data[1] == 0x00 && chunk_data[2] == 0x00; let chunk_data = if is_named { pumpkin_nbt::from_bytes::(std::io::Cursor::new(chunk_data)) } else { pumpkin_nbt::from_bytes_unnamed::(std::io::Cursor::new(chunk_data)) } .map_err(|e| ChunkParsingError::ErrorDeserializingChunk(e.to_string()))?; if chunk_data.x_pos != position.x || chunk_data.z_pos != position.y { return Err(ChunkParsingError::ErrorDeserializingChunk(format!( "Expected data for chunk {},{} but got it for {},{}!", position.x, position.y, chunk_data.x_pos, chunk_data.z_pos, ))); } let min_y_section = chunk_data.min_y_section; let max_y_section = chunk_data .sections .iter() .map(|s| s.y) .max() .unwrap_or(min_y_section as i8); let section_count = (max_y_section as i32 - min_y_section + 1).max(0) as usize; let mut block_lights = vec![LightContainer::Empty(0); section_count]; let mut sky_lights = vec![LightContainer::Empty(0); section_count]; let mut block_palettes = vec![BlockPalette::default(); section_count]; let mut biome_palettes = vec![BiomePalette::default(); section_count]; for section in chunk_data.sections { let index = (section.y as i32 - min_y_section) as usize; if index >= section_count { continue; } // When loading light data, missing data should default to 0 (no light) block_lights[index] = section .block_light .map_or(LightContainer::Empty(0), LightContainer::Full); sky_lights[index] = section .sky_light .map_or(LightContainer::Empty(0), LightContainer::Full); // Convert NBT to Palettes block_palettes[index] = section .block_states .map(BlockPalette::from_disk_nbt) .unwrap_or_default(); biome_palettes[index] = section .biomes .map(BiomePalette::from_disk_nbt) .unwrap_or_default(); } // Assemble the LightEngine let light_engine = ChunkLight { block_light: block_lights.into_boxed_slice(), sky_light: sky_lights.into_boxed_slice(), }; // Assemble the ChunkSections let min_y = section_coords::section_to_block(chunk_data.min_y_section); let (random_tick_sections, randomly_ticking_mask) = ChunkSections::build_random_tick_sections_cache(&block_palettes); let section = ChunkSections { count: block_palettes.len(), block_sections: RwLock::new(block_palettes.into_boxed_slice()), random_tick_sections: RwLock::new(random_tick_sections), randomly_ticking_mask: std::sync::atomic::AtomicU32::new(randomly_ticking_mask), biome_sections: RwLock::new(biome_palettes.into_boxed_slice()), min_y, }; Ok(Self { section, heightmap: std::sync::Mutex::new(chunk_data.heightmaps), x: position.x, z: position.y, // This chunk is read from disk, so it has not been modified dirty: AtomicBool::new(false), block_ticks: ChunkTickScheduler::from_iter(chunk_data.block_ticks), fluid_ticks: ChunkTickScheduler::from_iter(chunk_data.fluid_ticks), pending_block_entities: { let mut block_entities = FxHashMap::default(); for nbt in chunk_data.block_entities { if let Some(x) = nbt.get_int("x") && let Some(y) = nbt.get_int("y") && let Some(z) = nbt.get_int("z") { block_entities.insert(BlockPos::new(x, y, z), nbt); } } std::sync::Mutex::new(block_entities) }, light_engine: std::sync::Mutex::new(light_engine), light_populated: AtomicBool::new(chunk_data.light_correct), status: chunk_data.status, blending_data: None, }) } fn internal_to_bytes(&self) -> Result { fn extract_light_ref(light: Option<&LightContainer>) -> Option<&[u8]> { match light { Some(LightContainer::Full(data)) => Some(data.as_ref()), _ => None, } } let is_light_correct = self .light_populated .load(std::sync::atomic::Ordering::Relaxed); let block_entities_nbt = { let entities_guard = self.pending_block_entities.lock().unwrap(); entities_guard.values().cloned().collect::>() }; let light_lock = self.light_engine.lock().unwrap(); let heightmap_lock = self.heightmap.lock().unwrap(); let block_lock = self.section.block_sections.read().unwrap(); let biome_lock = self.section.biome_sections.read().unwrap(); let min_section_y = (self.section.min_y >> 4) as i8; let sections = (0..self.section.count) .map(|i| ChunkSectionNbtRef { y: i as i8 + min_section_y, block_states: Some(block_lock[i].to_disk_nbt()), biomes: Some(biome_lock[i].to_disk_nbt()), block_light: extract_light_ref(light_lock.block_light.get(i)), sky_light: extract_light_ref(light_lock.sky_light.get(i)), }) .collect::>(); let nbt_ref = ChunkNbtRef { data_version: WORLD_DATA_VERSION, x_pos: self.x, z_pos: self.z, min_y_section: section_coords::block_to_section(self.section.min_y), status: &self.status, heightmaps: &heightmap_lock, sections, block_ticks: &self.block_ticks.to_vec(), fluid_ticks: &self.fluid_ticks.to_vec(), block_entities: &block_entities_nbt, light_correct: is_light_correct, }; let mut result = Vec::new(); pumpkin_nbt::to_bytes(&nbt_ref, &mut result) .map_err(ChunkSerializingError::ErrorSerializingChunk)?; Ok(result.into()) } } impl PathFromLevelFolder for ChunkEntityData { #[inline] fn file_path(folder: &LevelFolder, file_name: &str) -> PathBuf { folder.entities_folder.join(file_name) } } impl Dirtiable for ChunkEntityData { #[inline] fn mark_dirty(&self, flag: bool) { self.dirty.store(flag, Ordering::Relaxed); } #[inline] fn is_dirty(&self) -> bool { self.dirty.load(Ordering::Relaxed) } } impl SingleChunkDataSerializer for ChunkEntityData { #[inline] fn from_bytes(bytes: &Bytes, pos: Vector2) -> Result { Self::internal_from_bytes(bytes, pos).map_err(ChunkReadingError::ParsingError) } #[inline] fn to_bytes( &self, ) -> Pin> + Send + '_>> { Box::pin(async move { self.internal_to_bytes().await }) } #[inline] fn position(&self) -> (i32, i32) { (self.x, self.z) } } impl ChunkEntityData { fn internal_from_bytes( chunk_data: &[u8], position: Vector2, ) -> Result { let is_named = chunk_data.len() >= 3 && chunk_data[0] == 0x0a && chunk_data[1] == 0x00 && chunk_data[2] == 0x00; let chunk_entity_data = if is_named { pumpkin_nbt::from_bytes::(std::io::Cursor::new(chunk_data)) } else { pumpkin_nbt::from_bytes_unnamed::(std::io::Cursor::new(chunk_data)) } .map_err(|e| ChunkParsingError::ErrorDeserializingChunk(e.to_string()))?; if chunk_entity_data.position[0] != position.x || chunk_entity_data.position[1] != position.y { return Err(ChunkParsingError::ErrorDeserializingChunk(format!( "Expected data for entity chunk {},{} but got it for {},{}!", position.x, position.y, chunk_entity_data.position[0], chunk_entity_data.position[1], ))); } Ok(Self { x: position.x, z: position.y, data: Mutex::new(chunk_entity_data.entities), dirty: AtomicBool::new(false), }) } async fn internal_to_bytes(&self) -> Result { let nbt = EntityNbt { data_version: WORLD_DATA_VERSION, position: [self.x, self.z], entities: self.data.lock().await.clone(), }; let mut result = Vec::new(); pumpkin_nbt::to_bytes(&nbt, &mut result) .map_err(ChunkSerializingError::ErrorSerializingChunk)?; Ok(result.into()) } } #[derive(Serialize, Deserialize)] struct ChunkSectionNBT { #[serde(skip_serializing_if = "Option::is_none")] block_states: Option, #[serde(skip_serializing_if = "Option::is_none")] biomes: Option, #[serde(rename = "BlockLight", skip_serializing_if = "Option::is_none")] block_light: Option>, #[serde(rename = "SkyLight", skip_serializing_if = "Option::is_none")] sky_light: Option>, #[serde(rename = "Y")] y: i8, } #[derive(Serialize)] struct ChunkSectionNbtRef<'a> { #[serde(skip_serializing_if = "Option::is_none")] block_states: Option, #[serde(skip_serializing_if = "Option::is_none")] biomes: Option, #[serde(rename = "BlockLight", skip_serializing_if = "Option::is_none")] block_light: Option<&'a [u8]>, #[serde(rename = "SkyLight", skip_serializing_if = "Option::is_none")] sky_light: Option<&'a [u8]>, #[serde(rename = "Y")] y: i8, } #[derive(Serialize, Deserialize, Debug, Clone)] pub struct ChunkSectionBiomes { #[serde( serialize_with = "nbt_long_array", skip_serializing_if = "Option::is_none" )] pub(crate) data: Option>, pub(crate) palette: Box<[u8]>, } #[derive(Serialize, Deserialize, Clone)] pub struct ChunkSectionBlockStates { #[serde( serialize_with = "nbt_long_array", skip_serializing_if = "Option::is_none" )] pub(crate) data: Option>, #[serde(with = "block_state_checked")] pub(crate) palette: Box<[BlockStateId]>, } mod block_state_checked { use pumpkin_data::BlockStateId; use serde::{Deserialize, Deserializer, Serialize, Serializer}; pub fn serialize( value: &[BlockStateId], serializer: S, ) -> Result { value .iter() .map(|v| BlockStateId::as_u16(*v)) .collect::>() .serialize(serializer) } pub fn deserialize<'de, D: Deserializer<'de>>( deserializer: D, ) -> Result, D::Error> { let raw = as Deserialize>::deserialize(deserializer)?; Ok(raw.iter().map(|v| BlockStateId::new_or_air(*v)).collect()) } } #[derive(Debug, Clone)] pub enum LightContainer { Empty(u8), Full(Box<[u8]>), } impl LightContainer { pub const DIM: usize = 16; pub const ARRAY_SIZE: usize = Self::DIM * Self::DIM * Self::DIM / 2; #[must_use] pub fn new_empty(default: u8) -> Self { assert!(default <= 15, "Default value must be between 0 and 15"); Self::Empty(default) } #[must_use] pub fn new(data: Box<[u8]>) -> Self { assert!( data.len() == Self::ARRAY_SIZE, "Data length must be {}", Self::ARRAY_SIZE ); Self::Full(data) } #[must_use] pub fn new_filled(default: u8) -> Self { assert!(default <= 15, "Default value must be between 0 and 15"); let value = default << 4 | default; Self::Full([value; Self::ARRAY_SIZE].into()) } #[must_use] pub const fn is_empty(&self) -> bool { matches!(self, Self::Empty(_)) } const fn index(x: usize, y: usize, z: usize) -> usize { y * 16 * 16 + z * 16 + x } #[must_use] pub fn get(&self, x: usize, y: usize, z: usize) -> u8 { match self { Self::Full(data) => { let index = Self::index(x, y, z); data[index >> 1] >> (4 * (index & 1)) & 0x0F } Self::Empty(default) => *default, } } pub fn set(&mut self, x: usize, y: usize, z: usize, value: u8) { match self { Self::Full(data) => { let index = Self::index(x, y, z); let mask = 0x0F << (4 * (index & 1)); data[index >> 1] &= !mask; data[index >> 1] |= value << (4 * (index & 1)); } Self::Empty(default) => { if value != *default { *self = Self::new_filled(*default); self.set(x, y, z, value); } } } } pub fn fill(&mut self, value: u8) { *self = Self::new_filled(value); } } impl Default for LightContainer { fn default() -> Self { Self::new_empty(15) } } #[derive(Serialize, Deserialize)] #[serde(rename_all = "PascalCase")] struct ChunkNbt { data_version: i32, #[serde(rename = "xPos")] x_pos: i32, #[serde(rename = "zPos")] z_pos: i32, #[serde(rename = "yPos")] min_y_section: i32, status: ChunkStatus, #[serde(rename = "sections")] sections: Vec, #[serde(default)] heightmaps: ChunkHeightmaps, #[serde(rename = "block_ticks", default)] block_ticks: Vec>, #[serde(rename = "fluid_ticks", default)] fluid_ticks: Vec>, #[serde(rename = "block_entities", default)] block_entities: Vec, #[serde(rename = "isLightOn", default)] light_correct: bool, } #[derive(Serialize)] #[serde(rename_all = "PascalCase")] struct ChunkNbtRef<'a> { data_version: i32, #[serde(rename = "xPos")] x_pos: i32, #[serde(rename = "zPos")] z_pos: i32, #[serde(rename = "yPos")] min_y_section: i32, status: &'a ChunkStatus, #[serde(rename = "sections")] sections: Vec>, heightmaps: &'a ChunkHeightmaps, #[serde(rename = "block_ticks")] block_ticks: &'a [ScheduledTick<&'static Block>], #[serde(rename = "fluid_ticks")] fluid_ticks: &'a [ScheduledTick<&'static Fluid>], #[serde(rename = "block_entities")] block_entities: &'a [NbtCompound], #[serde(rename = "isLightOn", default)] light_correct: bool, } #[derive(Serialize, Deserialize, Debug)] #[serde(rename_all = "PascalCase")] struct EntityNbt { data_version: i32, position: [i32; 2], entities: Vec, }