use std::borrow::Cow;
use std::collections::{BTreeMap, BTreeSet};
use std::mem;
use std::sync::atomic::{AtomicU32, Ordering};
use parking_lot::Mutex; use valence_nbt::{compound, Compound};
use valence_protocol::encode::{PacketWriter, WritePacket};
use valence_protocol::packets::play::chunk_data_s2c::ChunkDataBlockEntity;
use valence_protocol::packets::play::{
BlockEntityUpdateS2c, BlockUpdateS2c, ChunkDataS2c, ChunkDeltaUpdateS2c,
};
use valence_protocol::{BlockPos, BlockState, ChunkPos, Encode, VarInt, VarLong};
use valence_registry::biome::BiomeId;
use valence_registry::RegistryIdx;
use super::chunk::{
bit_width, check_biome_oob, check_block_oob, check_section_oob, BiomeContainer,
BlockStateContainer, Chunk, SECTION_BLOCK_COUNT,
};
use super::paletted_container::PalettedContainer;
use super::unloaded::{self, UnloadedChunk};
use super::{ChunkLayerInfo, ChunkLayerMessages, LocalMsg};
#[derive(Debug)]
pub struct LoadedChunk {
viewer_count: AtomicU32,
sections: Box<[Section]>,
block_entities: BTreeMap<u32, Compound>,
changed_block_entities: BTreeSet<u32>,
changed_biomes: bool,
cached_init_packets: Mutex<Vec<u8>>,
}
#[derive(Clone, Default, Debug)]
struct Section {
block_states: BlockStateContainer,
biomes: BiomeContainer,
section_updates: Vec<VarLong>,
}
impl Section {
fn count_non_air_blocks(&self) -> u16 {
let mut count = 0;
match &self.block_states {
PalettedContainer::Single(s) => {
if !s.is_air() {
count += SECTION_BLOCK_COUNT as u16;
}
}
PalettedContainer::Indirect(ind) => {
for i in 0..SECTION_BLOCK_COUNT {
if !ind.get(i).is_air() {
count += 1;
}
}
}
PalettedContainer::Direct(dir) => {
for s in dir.as_ref() {
if !s.is_air() {
count += 1;
}
}
}
}
count
}
}
impl LoadedChunk {
pub(crate) fn new(height: u32) -> Self {
Self {
viewer_count: AtomicU32::new(0),
sections: vec![Section::default(); height as usize / 16].into(),
block_entities: BTreeMap::new(),
changed_block_entities: BTreeSet::new(),
changed_biomes: false,
cached_init_packets: Mutex::new(vec![]),
}
}
pub(crate) fn insert(&mut self, mut chunk: UnloadedChunk) -> UnloadedChunk {
chunk.set_height(self.height());
let old_sections = self
.sections
.iter_mut()
.zip(chunk.sections)
.map(|(sect, other_sect)| {
sect.section_updates.clear();
unloaded::Section {
block_states: mem::replace(&mut sect.block_states, other_sect.block_states),
biomes: mem::replace(&mut sect.biomes, other_sect.biomes),
}
})
.collect();
let old_block_entities = mem::replace(&mut self.block_entities, chunk.block_entities);
self.changed_block_entities.clear();
self.changed_biomes = false;
self.cached_init_packets.get_mut().clear();
self.assert_no_changes();
UnloadedChunk {
sections: old_sections,
block_entities: old_block_entities,
}
}
pub(crate) fn remove(&mut self) -> UnloadedChunk {
let old_sections = self
.sections
.iter_mut()
.map(|sect| {
sect.section_updates.clear();
unloaded::Section {
block_states: mem::take(&mut sect.block_states),
biomes: mem::take(&mut sect.biomes),
}
})
.collect();
let old_block_entities = mem::take(&mut self.block_entities);
self.changed_block_entities.clear();
self.changed_biomes = false;
self.cached_init_packets.get_mut().clear();
self.assert_no_changes();
UnloadedChunk {
sections: old_sections,
block_entities: old_block_entities,
}
}
pub fn viewer_count(&self) -> u32 {
self.viewer_count.load(Ordering::Relaxed)
}
pub fn viewer_count_mut(&mut self) -> u32 {
*self.viewer_count.get_mut()
}
pub(crate) fn inc_viewer_count(&self) {
self.viewer_count.fetch_add(1, Ordering::Relaxed);
}
#[track_caller]
pub(crate) fn dec_viewer_count(&self) {
let old = self.viewer_count.fetch_sub(1, Ordering::Relaxed);
debug_assert_ne!(old, 0, "viewer count underflow!");
}
pub(crate) fn update_pre_client(
&mut self,
pos: ChunkPos,
info: &ChunkLayerInfo,
messages: &mut ChunkLayerMessages,
) {
if *self.viewer_count.get_mut() == 0 {
self.assert_no_changes();
return;
}
for (sect_y, sect) in self.sections.iter_mut().enumerate() {
match sect.section_updates.len() {
0 => {}
1 => {
let packed = sect.section_updates[0].0 as u64;
let offset_y = packed & 0b1111;
let offset_z = (packed >> 4) & 0b1111;
let offset_x = (packed >> 8) & 0b1111;
let block = packed >> 12;
let global_x = pos.x * 16 + offset_x as i32;
let global_y = info.min_y + sect_y as i32 * 16 + offset_y as i32;
let global_z = pos.z * 16 + offset_z as i32;
messages.send_local_infallible(LocalMsg::PacketAt { pos }, |buf| {
let mut writer = PacketWriter::new(buf, info.threshold);
writer.write_packet(&BlockUpdateS2c {
position: BlockPos::new(global_x, global_y, global_z),
block_id: VarInt(block as i32),
});
});
}
_ => {
let chunk_section_position = (pos.x as i64) << 42
| (pos.z as i64 & 0x3fffff) << 20
| (sect_y as i64 + info.min_y.div_euclid(16) as i64) & 0xfffff;
messages.send_local_infallible(LocalMsg::PacketAt { pos }, |buf| {
let mut writer = PacketWriter::new(buf, info.threshold);
writer.write_packet(&ChunkDeltaUpdateS2c {
chunk_section_position,
blocks: Cow::Borrowed(§.section_updates),
});
});
}
}
sect.section_updates.clear();
}
for &idx in &self.changed_block_entities {
let Some(nbt) = self.block_entities.get(&idx) else {
continue;
};
let x = idx % 16;
let z = (idx / 16) % 16;
let y = idx / 16 / 16;
let state = self.sections[y as usize / 16]
.block_states
.get(idx as usize % SECTION_BLOCK_COUNT);
let Some(kind) = state.block_entity_kind() else {
continue;
};
let global_x = pos.x * 16 + x as i32;
let global_y = info.min_y + y as i32;
let global_z = pos.z * 16 + z as i32;
messages.send_local_infallible(LocalMsg::PacketAt { pos }, |buf| {
let mut writer = PacketWriter::new(buf, info.threshold);
writer.write_packet(&BlockEntityUpdateS2c {
position: BlockPos::new(global_x, global_y, global_z),
kind: VarInt(kind as i32),
data: Cow::Borrowed(nbt),
});
});
}
self.changed_block_entities.clear();
if self.changed_biomes {
self.changed_biomes = false;
messages.send_local_infallible(LocalMsg::ChangeBiome { pos }, |buf| {
for sect in self.sections.iter() {
sect.biomes
.encode_mc_format(
&mut *buf,
|b| b.to_index() as _,
0,
3,
bit_width(info.biome_registry_len - 1),
)
.expect("paletted container encode should always succeed");
}
});
}
self.assert_no_changes();
}
pub(crate) fn write_init_packets(
&self,
mut writer: impl WritePacket,
pos: ChunkPos,
info: &ChunkLayerInfo,
) {
let mut init_packets = self.cached_init_packets.lock();
if init_packets.is_empty() {
let heightmaps = compound! {
};
let mut blocks_and_biomes: Vec<u8> = vec![];
for sect in self.sections.iter() {
sect.count_non_air_blocks()
.encode(&mut blocks_and_biomes)
.unwrap();
sect.block_states
.encode_mc_format(
&mut blocks_and_biomes,
|b| b.to_raw().into(),
4,
8,
bit_width(BlockState::max_raw().into()),
)
.expect("paletted container encode should always succeed");
sect.biomes
.encode_mc_format(
&mut blocks_and_biomes,
|b| b.to_index() as _,
0,
3,
bit_width(info.biome_registry_len - 1),
)
.expect("paletted container encode should always succeed");
}
let block_entities: Vec<_> = self
.block_entities
.iter()
.filter_map(|(&idx, nbt)| {
let x = idx % 16;
let z = idx / 16 % 16;
let y = idx / 16 / 16;
let kind = self.sections[y as usize / 16]
.block_states
.get(idx as usize % SECTION_BLOCK_COUNT)
.block_entity_kind();
kind.map(|kind| ChunkDataBlockEntity {
packed_xz: ((x << 4) | z) as i8,
y: y as i16 + info.min_y as i16,
kind: VarInt(kind as i32),
data: Cow::Borrowed(nbt),
})
})
.collect();
PacketWriter::new(&mut init_packets, info.threshold).write_packet(&ChunkDataS2c {
pos,
heightmaps: Cow::Owned(heightmaps),
blocks_and_biomes: &blocks_and_biomes,
block_entities: Cow::Owned(block_entities),
sky_light_mask: Cow::Borrowed(&[]),
block_light_mask: Cow::Borrowed(&[]),
empty_sky_light_mask: Cow::Borrowed(&[]),
empty_block_light_mask: Cow::Borrowed(&[]),
sky_light_arrays: Cow::Borrowed(&[]),
block_light_arrays: Cow::Borrowed(&[]),
})
}
writer.write_packet_bytes(&init_packets);
}
#[track_caller]
fn assert_no_changes(&self) {
#[cfg(debug_assertions)]
{
assert!(!self.changed_biomes);
assert!(self.changed_block_entities.is_empty());
for sect in self.sections.iter() {
assert!(sect.section_updates.is_empty());
}
}
}
}
impl Chunk for LoadedChunk {
fn height(&self) -> u32 {
self.sections.len() as u32 * 16
}
fn block_state(&self, x: u32, y: u32, z: u32) -> BlockState {
check_block_oob(self, x, y, z);
let idx = x + z * 16 + y % 16 * 16 * 16;
self.sections[y as usize / 16]
.block_states
.get(idx as usize)
}
fn set_block_state(&mut self, x: u32, y: u32, z: u32, block: BlockState) -> BlockState {
check_block_oob(self, x, y, z);
let sect_y = y / 16;
let sect = &mut self.sections[sect_y as usize];
let idx = x + z * 16 + y % 16 * 16 * 16;
let old_block = sect.block_states.set(idx as usize, block);
if block != old_block {
self.cached_init_packets.get_mut().clear();
if *self.viewer_count.get_mut() > 0 {
let compact = (block.to_raw() as i64) << 12 | (x << 8 | z << 4 | (y % 16)) as i64;
sect.section_updates.push(VarLong(compact));
}
}
old_block
}
fn fill_block_state_section(&mut self, sect_y: u32, block: BlockState) {
check_section_oob(self, sect_y);
let sect = &mut self.sections[sect_y as usize];
if let PalettedContainer::Single(b) = §.block_states {
if *b != block {
self.cached_init_packets.get_mut().clear();
if *self.viewer_count.get_mut() > 0 {
sect.section_updates.clear();
sect.section_updates.reserve_exact(SECTION_BLOCK_COUNT);
let block_bits = (block.to_raw() as i64) << 12;
for z in 0..16 {
for x in 0..16 {
let packed = block_bits | (x << 8 | z << 4 | sect_y as i64);
sect.section_updates.push(VarLong(packed));
}
}
}
}
} else {
let block_bits = (block.to_raw() as i64) << 12;
for z in 0..16 {
for x in 0..16 {
let idx = x + z * 16 + sect_y * (16 * 16);
if block != sect.block_states.get(idx as usize) {
self.cached_init_packets.get_mut().clear();
if *self.viewer_count.get_mut() > 0 {
let packed = block_bits | (x << 8 | z << 4 | sect_y) as i64;
sect.section_updates.push(VarLong(packed));
}
}
}
}
}
sect.block_states.fill(block);
}
fn block_entity(&self, x: u32, y: u32, z: u32) -> Option<&Compound> {
check_block_oob(self, x, y, z);
let idx = x + z * 16 + y * 16 * 16;
self.block_entities.get(&idx)
}
fn block_entity_mut(&mut self, x: u32, y: u32, z: u32) -> Option<&mut Compound> {
check_block_oob(self, x, y, z);
let idx = x + z * 16 + y * 16 * 16;
if let Some(be) = self.block_entities.get_mut(&idx) {
if *self.viewer_count.get_mut() > 0 {
self.changed_block_entities.insert(idx);
}
self.cached_init_packets.get_mut().clear();
Some(be)
} else {
None
}
}
fn set_block_entity(
&mut self,
x: u32,
y: u32,
z: u32,
block_entity: Option<Compound>,
) -> Option<Compound> {
check_block_oob(self, x, y, z);
let idx = x + z * 16 + y * 16 * 16;
match block_entity {
Some(nbt) => {
if *self.viewer_count.get_mut() > 0 {
self.changed_block_entities.insert(idx);
}
self.cached_init_packets.get_mut().clear();
self.block_entities.insert(idx, nbt)
}
None => {
let res = self.block_entities.remove(&idx);
if res.is_some() {
self.cached_init_packets.get_mut().clear();
}
res
}
}
}
fn clear_block_entities(&mut self) {
if self.block_entities.is_empty() {
return;
}
self.cached_init_packets.get_mut().clear();
if *self.viewer_count.get_mut() > 0 {
self.changed_block_entities
.extend(mem::take(&mut self.block_entities).into_keys());
} else {
self.block_entities.clear();
}
}
fn biome(&self, x: u32, y: u32, z: u32) -> BiomeId {
check_biome_oob(self, x, y, z);
let idx = x + z * 4 + y % 4 * 4 * 4;
self.sections[y as usize / 4].biomes.get(idx as usize)
}
fn set_biome(&mut self, x: u32, y: u32, z: u32, biome: BiomeId) -> BiomeId {
check_biome_oob(self, x, y, z);
let idx = x + z * 4 + y % 4 * 4 * 4;
let old_biome = self.sections[y as usize / 4]
.biomes
.set(idx as usize, biome);
if biome != old_biome {
self.cached_init_packets.get_mut().clear();
if *self.viewer_count.get_mut() > 0 {
self.changed_biomes = true;
}
}
old_biome
}
fn fill_biome_section(&mut self, sect_y: u32, biome: BiomeId) {
check_section_oob(self, sect_y);
let sect = &mut self.sections[sect_y as usize];
if let PalettedContainer::Single(b) = §.biomes {
if *b != biome {
self.cached_init_packets.get_mut().clear();
self.changed_biomes = *self.viewer_count.get_mut() > 0;
}
} else {
self.cached_init_packets.get_mut().clear();
self.changed_biomes = *self.viewer_count.get_mut() > 0;
}
sect.biomes.fill(biome);
}
fn shrink_to_fit(&mut self) {
self.cached_init_packets.get_mut().shrink_to_fit();
for sect in self.sections.iter_mut() {
sect.block_states.shrink_to_fit();
sect.biomes.shrink_to_fit();
sect.section_updates.shrink_to_fit();
}
}
}
#[cfg(test)]
mod tests {
use valence_protocol::ident;
use super::*;
#[test]
fn loaded_chunk_unviewed_no_changes() {
let mut chunk = LoadedChunk::new(512);
chunk.set_block(0, 10, 0, BlockState::MAGMA_BLOCK);
chunk.assert_no_changes();
chunk.set_biome(0, 0, 0, BiomeId::from_index(5));
chunk.assert_no_changes();
chunk.fill_block_states(BlockState::ACACIA_BUTTON);
chunk.assert_no_changes();
chunk.fill_biomes(BiomeId::from_index(42));
chunk.assert_no_changes();
}
#[test]
fn loaded_chunk_changes_clear_packet_cache() {
#[track_caller]
fn check<T>(chunk: &mut LoadedChunk, change: impl FnOnce(&mut LoadedChunk) -> T) {
let info = ChunkLayerInfo {
dimension_type_name: ident!("whatever").into(),
height: 512,
min_y: -16,
biome_registry_len: 200,
threshold: None,
};
let mut buf = vec![];
let mut writer = PacketWriter::new(&mut buf, None);
chunk.write_init_packets(&mut writer, ChunkPos::new(3, 4), &info);
assert!(!chunk.cached_init_packets.get_mut().is_empty());
change(chunk);
assert!(chunk.cached_init_packets.get_mut().is_empty());
chunk.write_init_packets(&mut writer, ChunkPos::new(3, 4), &info);
assert!(!chunk.cached_init_packets.get_mut().is_empty());
}
let mut chunk = LoadedChunk::new(512);
check(&mut chunk, |c| {
c.set_block_state(0, 4, 0, BlockState::ACACIA_WOOD)
});
check(&mut chunk, |c| c.set_biome(1, 2, 3, BiomeId::from_index(4)));
check(&mut chunk, |c| c.fill_biomes(BiomeId::DEFAULT));
check(&mut chunk, |c| c.fill_block_states(BlockState::WET_SPONGE));
check(&mut chunk, |c| {
c.set_block_entity(3, 40, 5, Some(compound! {}))
});
check(&mut chunk, |c| {
c.block_entity_mut(3, 40, 5).unwrap();
});
check(&mut chunk, |c| c.set_block_entity(3, 40, 5, None));
assert_eq!(
chunk.set_block_state(0, 0, 0, BlockState::WET_SPONGE),
BlockState::WET_SPONGE
);
assert!(!chunk.cached_init_packets.get_mut().is_empty());
}
}