valence/src/chunk.rs

560 lines
17 KiB
Rust
Raw Normal View History

2022-04-15 07:55:45 +10:00
// TODO: https://github.com/rust-lang/rust/issues/88581 for div_ceil
use std::collections::HashMap;
2022-04-15 07:55:45 +10:00
use std::io::Write;
2022-04-29 17:48:41 +10:00
use std::iter::FusedIterator;
use std::ops::Deref;
2022-04-15 07:55:45 +10:00
use bitvec::vec::BitVec;
use num::Integer;
use rayon::iter::{IntoParallelRefIterator, IntoParallelRefMutIterator, ParallelIterator};
2022-04-15 07:55:45 +10:00
2022-05-01 12:05:38 +10:00
use crate::block::BlockState;
2022-06-10 13:26:21 +10:00
use crate::packets::play::s2c::{
BlockChange, ChunkDataAndUpdateLight, ChunkDataHeightmaps, MultiBlockChange, S2cPlayPacket,
2022-04-15 07:55:45 +10:00
};
use crate::protocol::{Encode, Nbt};
use crate::var_int::VarInt;
2022-06-23 01:06:54 +10:00
use crate::var_long::VarLong;
2022-06-28 10:52:23 +10:00
use crate::{BiomeId, BlockPos, ChunkPos, DimensionId, Server, Ticks};
2022-04-15 07:55:45 +10:00
pub struct Chunks {
chunks: HashMap<ChunkPos, Chunk>,
server: Server,
2022-06-23 01:06:54 +10:00
dimension: DimensionId,
2022-04-29 17:48:41 +10:00
}
impl Chunks {
2022-06-23 01:06:54 +10:00
pub(crate) fn new(server: Server, dimension: DimensionId) -> Self {
Self {
chunks: HashMap::new(),
server,
2022-06-23 01:06:54 +10:00
dimension,
}
2022-04-29 17:48:41 +10:00
}
pub fn count(&self) -> usize {
self.chunks.len()
2022-04-29 17:48:41 +10:00
}
2022-06-23 01:06:54 +10:00
pub fn get(&self, pos: impl Into<ChunkPos>) -> Option<&Chunk> {
self.chunks.get(&pos.into())
2022-04-29 17:48:41 +10:00
}
pub fn clear(&mut self) {
self.chunks.clear();
2022-04-29 17:48:41 +10:00
}
pub fn iter(&self) -> impl FusedIterator<Item = (ChunkPos, &Chunk)> + Clone + '_ {
self.chunks.iter().map(|(&pos, chunk)| (pos, chunk))
2022-04-29 17:48:41 +10:00
}
pub fn par_iter(&self) -> impl ParallelIterator<Item = (ChunkPos, &Chunk)> + Clone + '_ {
self.chunks.par_iter().map(|(&pos, chunk)| (pos, chunk))
2022-04-29 17:48:41 +10:00
}
2022-06-23 01:06:54 +10:00
pub fn get_block_state(&self, pos: impl Into<BlockPos>) -> Option<BlockState> {
let pos = pos.into();
2022-06-25 09:11:15 +10:00
let chunk_pos = ChunkPos::from(pos);
2022-06-23 01:06:54 +10:00
let chunk = self.get(chunk_pos)?;
let min_y = self.server.dimension(self.dimension).min_y;
let y = pos.y.checked_sub(min_y)?.try_into().ok()?;
if y < chunk.height() {
Some(chunk.get_block_state(
pos.x.rem_euclid(16) as usize,
y,
pos.z.rem_euclid(16) as usize,
))
} else {
None
}
}
}
2022-04-29 17:48:41 +10:00
impl<'a> ChunksMut<'a> {
pub(crate) fn new(chunks: &'a mut Chunks) -> Self {
Self(chunks)
2022-04-29 17:48:41 +10:00
}
2022-06-25 09:11:15 +10:00
pub fn reborrow(&mut self) -> ChunksMut {
ChunksMut(self.0)
}
2022-05-17 04:53:21 +10:00
pub fn create(&mut self, pos: impl Into<ChunkPos>) -> bool {
2022-06-23 01:06:54 +10:00
let section_count = (self.server.dimension(self.dimension).height / 16) as u32;
let chunk = Chunk::new(section_count, self.server.current_tick());
2022-05-17 04:53:21 +10:00
self.0.chunks.insert(pos.into(), chunk).is_none()
2022-04-29 17:48:41 +10:00
}
pub fn delete(&mut self, pos: ChunkPos) -> bool {
self.0.chunks.remove(&pos).is_some()
2022-04-29 17:48:41 +10:00
}
2022-06-23 01:06:54 +10:00
pub fn get_mut(&mut self, pos: impl Into<ChunkPos>) -> Option<ChunkMut> {
self.0.chunks.get_mut(&pos.into()).map(ChunkMut)
2022-04-29 17:48:41 +10:00
}
pub fn iter_mut(&mut self) -> impl FusedIterator<Item = (ChunkPos, ChunkMut)> + '_ {
self.0
.chunks
.iter_mut()
.map(|(&pos, chunk)| (pos, ChunkMut(chunk)))
}
pub fn par_iter_mut(&mut self) -> impl ParallelIterator<Item = (ChunkPos, ChunkMut)> + '_ {
self.0
.chunks
.par_iter_mut()
.map(|(&pos, chunk)| (pos, ChunkMut(chunk)))
2022-04-29 17:48:41 +10:00
}
2022-06-23 01:06:54 +10:00
pub fn set_block_state(&mut self, pos: impl Into<BlockPos>, block: BlockState) -> bool {
let pos = pos.into();
2022-06-25 09:11:15 +10:00
let chunk_pos = ChunkPos::from(pos);
2022-06-23 01:06:54 +10:00
if let Some(chunk) = self.0.chunks.get_mut(&chunk_pos) {
let min_y = self.0.server.dimension(self.0.dimension).min_y;
if let Some(y) = pos.y.checked_sub(min_y).and_then(|y| y.try_into().ok()) {
if y < chunk.height() {
ChunkMut(chunk).set_block_state(
pos.x.rem_euclid(16) as usize,
y,
pos.z.rem_euclid(16) as usize,
block,
);
return true;
}
}
}
false
}
2022-04-29 17:48:41 +10:00
}
pub struct ChunksMut<'a>(&'a mut Chunks);
impl<'a> Deref for ChunksMut<'a> {
type Target = Chunks;
fn deref(&self) -> &Self::Target {
self.0
}
}
2022-04-29 17:48:41 +10:00
2022-04-15 07:55:45 +10:00
pub struct Chunk {
sections: Box<[ChunkSection]>,
// TODO block_entities: HashMap<u32, BlockEntity>,
/// The MOTION_BLOCKING heightmap
2022-04-15 07:55:45 +10:00
heightmap: Vec<i64>,
created_tick: Ticks,
2022-04-15 07:55:45 +10:00
}
impl Chunk {
pub(crate) fn new(section_count: u32, current_tick: Ticks) -> Self {
2022-04-15 07:55:45 +10:00
let sect = ChunkSection {
2022-06-23 01:06:54 +10:00
blocks: [BlockState::AIR.to_raw(); 4096],
modified_count: 1, // Must be >0 so the chunk is initialized.
2022-05-01 12:05:38 +10:00
biomes: [BiomeId::default(); 64],
2022-04-15 07:55:45 +10:00
compact_data: Vec::new(),
};
let mut chunk = Self {
sections: vec![sect; section_count as usize].into(),
2022-04-15 07:55:45 +10:00
heightmap: Vec::new(),
created_tick: current_tick,
2022-04-15 07:55:45 +10:00
};
ChunkMut(&mut chunk).apply_modifications();
2022-04-15 07:55:45 +10:00
chunk
}
pub fn created_tick(&self) -> Ticks {
self.created_tick
2022-04-15 07:55:45 +10:00
}
pub fn height(&self) -> usize {
self.sections.len() * 16
}
2022-05-01 12:05:38 +10:00
pub fn get_block_state(&self, x: usize, y: usize, z: usize) -> BlockState {
2022-04-15 07:55:45 +10:00
if x < 16 && y < self.height() && z < 16 {
2022-06-23 01:06:54 +10:00
BlockState::from_raw_unchecked(
self.sections[y / 16].blocks[x + z * 16 + y % 16 * 16 * 16] & BLOCK_STATE_MASK,
)
2022-04-15 07:55:45 +10:00
} else {
2022-05-01 12:05:38 +10:00
BlockState::AIR
2022-04-15 07:55:45 +10:00
}
}
pub fn get_biome(&self, x: usize, y: usize, z: usize) -> BiomeId {
if x < 4 && y < self.height() / 4 && z < 4 {
2022-05-01 12:05:38 +10:00
self.sections[y / 4].biomes[x + z * 4 + y % 4 * 4 * 4]
2022-04-15 07:55:45 +10:00
} else {
BiomeId::default()
}
}
/// Gets the chunk data packet for this chunk with the given position. This
/// does not include unapplied changes.
pub(crate) fn chunk_data_packet(&self, pos: ChunkPos) -> ChunkDataAndUpdateLight {
2022-04-15 07:55:45 +10:00
let mut blocks_and_biomes = Vec::new();
for sect in self.sections.iter() {
blocks_and_biomes.extend_from_slice(&sect.compact_data);
2022-04-15 07:55:45 +10:00
}
ChunkDataAndUpdateLight {
chunk_x: pos.x,
chunk_z: pos.z,
heightmaps: Nbt(ChunkDataHeightmaps {
motion_blocking: self.heightmap.clone(),
}),
2022-04-15 07:55:45 +10:00
blocks_and_biomes,
block_entities: Vec::new(), // TODO
trust_edges: true,
// sky_light_mask: bitvec![u64, _; 1; section_count + 2],
sky_light_mask: BitVec::new(),
2022-04-15 07:55:45 +10:00
block_light_mask: BitVec::new(),
empty_sky_light_mask: BitVec::new(),
empty_block_light_mask: BitVec::new(),
// sky_light_arrays: vec![[0xff; 2048]; section_count + 2],
sky_light_arrays: Vec::new(),
2022-04-15 07:55:45 +10:00
block_light_arrays: Vec::new(),
}
}
2022-06-23 01:06:54 +10:00
/// Returns unapplied changes to this chunk as block change packets through
/// the provided closure.
pub(crate) fn block_change_packets(
&self,
pos: ChunkPos,
min_y: i32,
mut packet: impl FnMut(BlockChangePacket),
) {
for (sect_y, sect) in self.sections.iter().enumerate() {
if sect.modified_count == 1 {
let (idx, &block) = sect
.blocks
.iter()
.enumerate()
.find(|&(_, &b)| b & !BLOCK_STATE_MASK != 0)
.expect("invalid modified count");
let global_x = pos.x * 16 + (idx % 16) as i32;
let global_y = sect_y as i32 * 16 + (idx / (16 * 16)) as i32 + min_y;
let global_z = pos.z * 16 + (idx / 16 % 16) as i32;
packet(BlockChangePacket::Single(BlockChange {
location: BlockPos::new(global_x, global_y, global_z),
block_id: VarInt((block & BLOCK_STATE_MASK).into()),
}));
} else if sect.modified_count > 1 {
let mut blocks = Vec::new();
for y in 0..16 {
for z in 0..16 {
for x in 0..16 {
let block =
sect.blocks[x as usize + z as usize * 16 + y as usize * 16 * 16];
if block & !BLOCK_STATE_MASK != 0 {
blocks.push(VarLong(
((block & BLOCK_STATE_MASK) as i64) << 12
| (x << 8 | z << 4 | y),
))
}
}
}
}
let chunk_section_position = (pos.x as i64) << 42
| (pos.z as i64 & 0x3fffff) << 20
2022-06-25 09:11:15 +10:00
| (sect_y as i64 + min_y.div_euclid(16) as i64) & 0xfffff;
2022-04-15 07:55:45 +10:00
2022-06-23 01:06:54 +10:00
packet(BlockChangePacket::Multi(MultiBlockChange {
chunk_section_position,
invert_trust_edges: false,
blocks,
}));
}
}
2022-04-15 07:55:45 +10:00
}
}
pub struct ChunkMut<'a>(&'a mut Chunk);
impl<'a> Deref for ChunkMut<'a> {
type Target = Chunk;
fn deref(&self) -> &Self::Target {
self.0
}
}
impl<'a> ChunkMut<'a> {
pub fn set_block_state(&mut self, x: usize, y: usize, z: usize, block: BlockState) {
2022-06-23 01:06:54 +10:00
assert!(
x < 16 && y < self.height() && z < 16,
"the chunk block coordinates must be within bounds"
);
let sect = &mut self.0.sections[y / 16];
let idx = x + z * 16 + y % 16 * 16 * 16;
if block.to_raw() != sect.blocks[idx] & BLOCK_STATE_MASK {
if sect.blocks[idx] & !BLOCK_STATE_MASK == 0 {
sect.modified_count += 1;
}
2022-06-23 01:06:54 +10:00
sect.blocks[idx] = block.to_raw() | !BLOCK_STATE_MASK;
// TODO: if the block type was modified and the old block type
// could be a block entity, then the block entity at this
// position must be cleared.
}
}
pub fn set_biome(&mut self, x: usize, y: usize, z: usize, b: BiomeId) {
2022-06-23 01:06:54 +10:00
assert!(
x < 4 && y < self.height() / 4 && z < 4,
"the chunk biome coordinates must be within bounds"
);
self.0.sections[y / 4].biomes[x + z * 4 + y % 4 * 4 * 4] = b;
}
2022-04-15 07:55:45 +10:00
pub(crate) fn apply_modifications(&mut self) {
2022-06-23 01:06:54 +10:00
let mut any_modified = false;
for sect in self.0.sections.iter_mut() {
if sect.modified_count > 0 {
sect.modified_count = 0;
any_modified = true;
sect.compact_data.clear();
let mut non_air_block_count: i16 = 0;
for b in &mut sect.blocks {
*b &= BLOCK_STATE_MASK;
if !BlockState::from_raw_unchecked(*b).is_air() {
non_air_block_count += 1;
}
2022-04-15 07:55:45 +10:00
}
2022-06-23 01:06:54 +10:00
non_air_block_count.encode(&mut sect.compact_data).unwrap();
encode_paletted_container(
sect.blocks.iter().cloned(),
4,
9,
log2_ceil((BlockState::max_raw() + 1) as usize),
&mut sect.compact_data,
)
.unwrap();
// TODO: The direct bits per idx changes depending on the number of biomes in
// the biome registry.
encode_paletted_container(
sect.biomes.iter().map(|b| b.0),
0,
4,
6,
&mut sect.compact_data,
)
.unwrap();
2022-04-15 07:55:45 +10:00
}
2022-06-23 01:06:54 +10:00
}
2022-04-15 07:55:45 +10:00
2022-06-23 01:06:54 +10:00
if any_modified {
build_heightmap(&self.0.sections, &mut self.0.heightmap);
2022-04-15 07:55:45 +10:00
}
}
}
#[derive(Clone, Debug)]
pub(crate) enum BlockChangePacket {
Single(BlockChange),
Multi(MultiBlockChange),
}
2022-06-10 13:26:21 +10:00
impl From<BlockChangePacket> for S2cPlayPacket {
2022-04-15 07:55:45 +10:00
fn from(p: BlockChangePacket) -> Self {
match p {
BlockChangePacket::Single(p) => p.into(),
BlockChangePacket::Multi(p) => p.into(),
}
}
}
/// A 16x16x16 section of blocks, biomes, and light in a chunk.
#[derive(Clone)]
struct ChunkSection {
2022-06-23 01:06:54 +10:00
/// The block states in this section, stored in x, z, y order.
/// The most significant bit is used to indicate if this block has been
/// modified.
blocks: [u16; 4096],
/// The number of modified blocks
modified_count: u16,
2022-05-01 12:05:38 +10:00
biomes: [BiomeId; 64],
2022-04-15 07:55:45 +10:00
compact_data: Vec<u8>,
}
2022-06-23 01:06:54 +10:00
const BLOCK_STATE_MASK: u16 = 0x7fff;
const _: () = assert!(
BlockState::max_raw() <= BLOCK_STATE_MASK,
"There is not enough space in the block state type to store the modified bit. A bit array \
separate from the block state array should be created to keep track of modified blocks in \
the chunk section."
);
2022-04-15 07:55:45 +10:00
/// Builds the MOTION_BLOCKING heightmap.
fn build_heightmap(sections: &[ChunkSection], heightmap: &mut Vec<i64>) {
let height = sections.len() * 16;
let bits_per_val = log2_ceil(height);
let vals_per_u64 = 64 / bits_per_val;
2022-04-29 17:48:41 +10:00
let u64_count = Integer::div_ceil(&256, &vals_per_u64);
2022-04-15 07:55:45 +10:00
heightmap.clear();
2022-04-29 17:48:41 +10:00
heightmap.resize(u64_count, 0);
2022-04-15 07:55:45 +10:00
for x in 0..16 {
for z in 0..16 {
for y in (0..height).rev() {
2022-06-23 01:06:54 +10:00
let block = BlockState::from_raw_unchecked(
sections[y / 16].blocks[x + z * 16 + y % 16 * 16 * 16] & BLOCK_STATE_MASK,
);
2022-04-15 07:55:45 +10:00
// TODO: is_solid || is_fluid heuristic for motion blocking.
2022-05-01 12:05:38 +10:00
if !block.is_air() {
2022-04-15 07:55:45 +10:00
let column_height = y as u64;
let i = x * 16 + z; // TODO: X or Z major?
heightmap[i / vals_per_u64] |=
(column_height << (i % vals_per_u64 * bits_per_val)) as i64;
break;
}
}
}
}
}
fn encode_paletted_container(
2022-05-17 04:53:21 +10:00
mut entries: impl ExactSizeIterator<Item = u16> + Clone,
2022-04-15 07:55:45 +10:00
min_bits_per_idx: usize,
direct_threshold: usize,
direct_bits_per_idx: usize,
w: &mut impl Write,
) -> anyhow::Result<()> {
let mut palette = Vec::new();
2022-05-01 12:05:38 +10:00
for entry in entries.clone() {
2022-04-15 07:55:45 +10:00
if !palette.contains(&entry) {
palette.push(entry);
}
}
let bits_per_idx = log2_ceil(palette.len());
(bits_per_idx as u8).encode(w)?;
if bits_per_idx == 0 {
// Single value case
debug_assert_eq!(palette.len(), 1);
VarInt(palette[0] as i32).encode(w)?;
VarInt(0).encode(w)?; // data array length
} else if bits_per_idx >= direct_threshold {
// Direct case
// Skip the palette
let idxs_per_u64 = 64 / direct_bits_per_idx;
2022-04-29 17:48:41 +10:00
let u64_count = Integer::div_ceil(&entries.len(), &idxs_per_u64);
2022-04-15 07:55:45 +10:00
2022-04-29 17:48:41 +10:00
VarInt(u64_count as i32).encode(w)?;
2022-04-15 07:55:45 +10:00
2022-05-17 04:53:21 +10:00
for _ in 0..idxs_per_u64 {
2022-04-15 07:55:45 +10:00
let mut val = 0u64;
for i in 0..idxs_per_u64 {
2022-05-17 04:53:21 +10:00
if let Some(entry) = entries.next() {
val |= (entry as u64) << (i * direct_bits_per_idx);
}
2022-04-15 07:55:45 +10:00
}
val.encode(w)?;
}
} else {
// Indirect case
VarInt(palette.len() as i32).encode(w)?;
for &val in &palette {
VarInt(val as i32).encode(w)?;
}
let bits_per_idx = bits_per_idx.max(min_bits_per_idx);
let idxs_per_u64 = 64 / bits_per_idx;
2022-04-29 17:48:41 +10:00
let u64_count = Integer::div_ceil(&entries.len(), &idxs_per_u64);
2022-04-15 07:55:45 +10:00
2022-04-29 17:48:41 +10:00
VarInt(u64_count as i32).encode(w)?;
2022-04-15 07:55:45 +10:00
2022-05-17 04:53:21 +10:00
for _ in 0..u64_count {
2022-04-15 07:55:45 +10:00
let mut val = 0u64;
for i in 0..idxs_per_u64 {
2022-05-17 04:53:21 +10:00
if let Some(entry) = entries.next() {
let palette_idx = palette
.iter()
.position(|&e| e == entry)
2022-05-17 19:58:43 +10:00
.expect("entry should be in the palette")
as u64;
2022-05-17 04:53:21 +10:00
val |= palette_idx << (i * bits_per_idx);
2022-05-17 19:58:43 +10:00
}
2022-04-15 07:55:45 +10:00
}
val.encode(w)?;
}
}
Ok(())
}
/// Calculates the log base 2 rounded up.
fn log2_ceil(n: usize) -> usize {
n.next_power_of_two().trailing_zeros() as usize
}
2022-06-23 01:06:54 +10:00
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn set_get() {
let mut chunk = Chunk::new(16, 0);
let mut chunk = ChunkMut(&mut chunk);
chunk.set_block_state(1, 2, 3, BlockState::CAKE);
assert_eq!(chunk.get_block_state(1, 2, 3), BlockState::CAKE);
chunk.set_biome(1, 2, 3, BiomeId(7));
assert_eq!(chunk.get_biome(1, 2, 3), BiomeId(7));
}
#[test]
#[should_panic]
fn block_state_oob() {
let mut chunk = Chunk::new(16, 0);
let mut chunk = ChunkMut(&mut chunk);
chunk.set_block_state(16, 0, 0, BlockState::CAKE);
}
#[test]
#[should_panic]
fn biome_oob() {
let mut chunk = Chunk::new(16, 0);
let mut chunk = ChunkMut(&mut chunk);
chunk.set_biome(4, 0, 0, BiomeId(0));
}
}