Read and Write light data using Anvil

Now we are reading and writing light data to and from the anvil format and send it in the chunk and light packet
This commit is contained in:
Alexander Medvedev 2025-04-10 19:09:27 +02:00
parent f0636bb924
commit fbd95cb693
9 changed files with 79 additions and 43 deletions

View File

@ -60,4 +60,4 @@ serde_json = "1.0"
async-trait = "0.1" async-trait = "0.1"
tempfile = "3.17.1" tempfile = "3.19.1"

View File

@ -29,11 +29,11 @@ aes = "0.8"
cfb8 = "0.8" cfb8 = "0.8"
# compression # compression
async-compression = { version = "0.4.20", features = ["tokio", "zlib"] } async-compression = { version = "0.4.22", features = ["tokio", "zlib"] }
take_mut = "0.2.2" take_mut = "0.2.2"
bitflags = "2.9.0" bitflags = "2.9.0"
[dev-dependencies] [dev-dependencies]
# Validate correctness # Validate correctness
flate2 = "1.1.0" flate2 = "1.1.1"

View File

@ -8,10 +8,7 @@ use crate::{
use pumpkin_data::packet::clientbound::PLAY_LEVEL_CHUNK_WITH_LIGHT; use pumpkin_data::packet::clientbound::PLAY_LEVEL_CHUNK_WITH_LIGHT;
use pumpkin_macros::packet; use pumpkin_macros::packet;
use pumpkin_world::chunk::{ use pumpkin_world::chunk::{ChunkData, palette::NetworkPalette};
ChunkData,
palette::{BlockPalette, NetworkPalette},
};
#[packet(PLAY_LEVEL_CHUNK_WITH_LIGHT)] #[packet(PLAY_LEVEL_CHUNK_WITH_LIGHT)]
pub struct CChunkData<'a>(pub &'a ChunkData); pub struct CChunkData<'a>(pub &'a ChunkData);
@ -45,21 +42,40 @@ impl ClientPacket for CChunkData<'_> {
} }
let mut data_buf = Vec::new(); let mut data_buf = Vec::new();
let mut light_buf = Vec::new();
for section in self.0.section.sections.iter() { let mut sky_light_buf = Vec::new();
// 2 blocks per byte for block lights let mut sky_light_empty_mask = 0;
let chunk_light_len = BlockPalette::VOLUME / 2; let mut sky_light_mask = 0;
// TODO: Implement, currently default to full bright let mut block_light_buf = Vec::new();
let chunk_light = vec![0xFFu8; chunk_light_len]; let mut block_light_empty_mask = 0;
let mut block_light_mask = 0;
light_buf.write_var_int(&chunk_light_len.try_into().map_err(|_| { for (i, section) in self.0.section.sections.iter().enumerate() {
WritingError::Message(format!( // Write sky light
"{} is not representable as a VarInt!", if let Some(sky_light) = &section.sky_light {
chunk_light_len let mut buf = Vec::new();
)) buf.write_var_int(&sky_light.len().try_into().map_err(|_| {
WritingError::Message("sky_light not representable as a VarInt!".to_string())
})?)?; })?)?;
light_buf.write_slice(&chunk_light)?; buf.write_slice(sky_light)?;
sky_light_buf.push(buf);
sky_light_mask |= 1 << i;
} else {
sky_light_empty_mask |= 1 << i;
}
// Write block light
if let Some(block_light) = &section.block_light {
let mut buf = Vec::new();
buf.write_var_int(&block_light.len().try_into().map_err(|_| {
WritingError::Message("block_light not representable as a VarInt!".to_string())
})?)?;
buf.write_slice(block_light)?;
block_light_buf.push(buf);
block_light_mask |= 1 << i;
} else {
block_light_empty_mask |= 1 << i;
}
// Block count // Block count
let non_empty_block_count = section.block_states.non_air_block_count() as i16; let non_empty_block_count = section.block_states.non_air_block_count() as i16;
@ -131,27 +147,29 @@ impl ClientPacket for CChunkData<'_> {
// TODO: block entities // TODO: block entities
write.write_var_int(&VarInt(0))?; write.write_var_int(&VarInt(0))?;
// Sky Light Mask
// All of the chunks, this is not optimal and uses way more data than needed but will be // All of the chunks, this is not optimal and uses way more data than needed but will be
// overhauled with a full lighting system. // overhauled with a full lighting system.
write.write_bitset(&BitSet(Box::new([0b01111111111111111111111110])))?;
// Sky Light Mask
write.write_bitset(&BitSet(Box::new([sky_light_mask])))?;
// Block Light Mask // Block Light Mask
write.write_bitset(&BitSet(Box::new([0])))?; write.write_bitset(&BitSet(Box::new([block_light_mask])))?;
// Empty Sky Light Mask // Empty Sky Light Mask
write.write_bitset(&BitSet(Box::new([0])))?; write.write_bitset(&BitSet(Box::new([sky_light_empty_mask])))?;
// Empty Block Light Mask // Empty Block Light Mask
write.write_bitset(&BitSet(Box::new([0])))?; write.write_bitset(&BitSet(Box::new([block_light_empty_mask])))?;
// Sky light // Sky light
write.write_var_int(&self.0.section.sections.len().try_into().map_err(|_| { write.write_var_int(&VarInt(sky_light_buf.len() as i32))?;
WritingError::Message(format!( for sky_buf in sky_light_buf {
"{} is not representable as a VarInt!", write.write_slice(&sky_buf)?;
self.0.section.sections.len() }
))
})?)?;
write.write_slice(&light_buf)?;
// Block Lighting // Block Light
write.write_var_int(&VarInt(0)) write.write_var_int(&VarInt(block_light_buf.len() as i32))?;
for block_buf in block_light_buf {
write.write_slice(&block_buf)?;
}
Ok(())
} }
} }

View File

@ -9,7 +9,7 @@ pumpkin-protocol = { path = "../pumpkin-protocol" }
pumpkin-nbt = { path = "../pumpkin-nbt" } pumpkin-nbt = { path = "../pumpkin-nbt" }
pumpkin-util = { path = "../pumpkin-util" } pumpkin-util = { path = "../pumpkin-util" }
indexmap = { version = "2.8", features = ["serde"] } indexmap = { version = "2.9", features = ["serde"] }
serde.workspace = true serde.workspace = true
serde_json.workspace = true serde_json.workspace = true

View File

@ -40,7 +40,7 @@ zstd = "0.13.3"
itertools = "0.14.0" itertools = "0.14.0"
file-guard = "0.2" file-guard = "0.2"
indexmap = "2.8" indexmap = "2.9"
enum_dispatch = "0.3" enum_dispatch = "0.3"
noise = "0.9" noise = "0.9"
derive-getters = "0.5.0" derive-getters = "0.5.0"
@ -54,7 +54,7 @@ tokio-util = { version = "0.7.14", features = ["rt"] }
criterion = { version = "0.5", features = ["html_reports", "async_tokio"] } criterion = { version = "0.5", features = ["html_reports", "async_tokio"] }
temp-dir = "0.1.14" temp-dir = "0.1.14"
# Print log info inside tests when needed # Print log info inside tests when needed
env_logger = "0.11.7" env_logger = "0.11.8"
# Allows us to modify the config # Allows us to modify the config
pumpkin-config = { path = "../pumpkin-config", features = ["test_helper"] } pumpkin-config = { path = "../pumpkin-config", features = ["test_helper"] }
# Deserialize NaN and Inf # Deserialize NaN and Inf

View File

@ -827,6 +827,8 @@ pub fn chunk_to_bytes(chunk_data: &ChunkData) -> Result<Vec<u8>, ChunkSerializin
y: i as i8 + section_coords::block_to_section(chunk_data.section.min_y) as i8, y: i as i8 + section_coords::block_to_section(chunk_data.section.min_y) as i8,
block_states, block_states,
biomes, biomes,
block_light: section.block_light.clone(), // :c
sky_light: section.sky_light.clone(), // :c
}); });
} }

View File

@ -54,6 +54,8 @@ impl ChunkData {
.map(|section| SubChunk { .map(|section| SubChunk {
block_states: BlockPalette::from_disk_nbt(section.block_states), block_states: BlockPalette::from_disk_nbt(section.block_states),
biomes: BiomePalette::from_disk_nbt(section.biomes), biomes: BiomePalette::from_disk_nbt(section.biomes),
block_light: section.block_light,
sky_light: section.sky_light,
}) })
.collect(); .collect();
let min_y = section_coords::section_to_block(chunk_data.min_y_section); let min_y = section_coords::section_to_block(chunk_data.min_y_section);
@ -101,11 +103,10 @@ impl ChunkData {
struct ChunkSectionNBT { struct ChunkSectionNBT {
block_states: ChunkSectionBlockStates, block_states: ChunkSectionBlockStates,
biomes: ChunkSectionBiomes, biomes: ChunkSectionBiomes,
// TODO #[serde(rename = "BlockLight", skip_serializing_if = "Option::is_none")]
// #[serde(rename = "BlockLight", skip_serializing_if = "Option::is_none")] block_light: Option<Box<[u8]>>,
// block_light: Option<Box<[u8]>>, #[serde(rename = "SkyLight", skip_serializing_if = "Option::is_none")]
// #[serde(rename = "SkyLight", skip_serializing_if = "Option::is_none")] sky_light: Option<Box<[u8]>>,
// sky_light: Option<Box<[u8]>>,
#[serde(rename = "Y")] #[serde(rename = "Y")]
y: i8, y: i8,
} }

View File

@ -129,6 +129,7 @@ pub struct ChunkSections {
impl ChunkSections { impl ChunkSections {
#[cfg(test)] #[cfg(test)]
pub fn dump_blocks(&self) -> Vec<u16> { pub fn dump_blocks(&self) -> Vec<u16> {
// TODO: this is not optimal, we could use rust iters
let mut dump = Vec::new(); let mut dump = Vec::new();
for section in self.sections.iter() { for section in self.sections.iter() {
section.block_states.for_each(|raw_id| { section.block_states.for_each(|raw_id| {
@ -140,6 +141,7 @@ impl ChunkSections {
#[cfg(test)] #[cfg(test)]
pub fn dump_biomes(&self) -> Vec<u8> { pub fn dump_biomes(&self) -> Vec<u8> {
// TODO: this is not optimal, we could use rust iters
let mut dump = Vec::new(); let mut dump = Vec::new();
for section in self.sections.iter() { for section in self.sections.iter() {
section.biomes.for_each(|raw_id| { section.biomes.for_each(|raw_id| {
@ -154,6 +156,19 @@ impl ChunkSections {
pub struct SubChunk { pub struct SubChunk {
pub block_states: BlockPalette, pub block_states: BlockPalette,
pub biomes: BiomePalette, pub biomes: BiomePalette,
pub block_light: Option<Box<[u8]>>,
pub sky_light: Option<Box<[u8]>>,
}
impl SubChunk {
pub fn max_light() -> Self {
let chunk_light_len = BlockPalette::VOLUME / 2;
Self {
block_light: Some(vec![0xFFu8; chunk_light_len].into_boxed_slice()),
sky_light: Some(vec![0xFFu8; chunk_light_len].into_boxed_slice()),
..Default::default()
}
}
} }
#[derive(Deserialize, Serialize, Debug, Clone)] #[derive(Deserialize, Serialize, Debug, Clone)]

View File

@ -43,7 +43,7 @@ impl WorldGenerator for VanillaGenerator {
.unwrap(); .unwrap();
let sub_chunks = generation_settings.shape.height as usize / BlockPalette::SIZE; let sub_chunks = generation_settings.shape.height as usize / BlockPalette::SIZE;
let sections = (0..sub_chunks).map(|_| SubChunk::default()).collect(); let sections = (0..sub_chunks).map(|_| SubChunk::max_light()).collect();
let mut sections = ChunkSections::new(sections, generation_settings.shape.min_y as i32); let mut sections = ChunkSections::new(sections, generation_settings.shape.min_y as i32);
let mut proto_chunk = ProtoChunk::new( let mut proto_chunk = ProtoChunk::new(