valence_server/layer/chunk/
chunk.rs

1use valence_nbt::Compound;
2use valence_protocol::BlockState;
3use valence_registry::biome::BiomeId;
4
5use super::paletted_container::PalettedContainer;
6
7/// Common operations on chunks. Notable implementors are
8/// [`LoadedChunk`](super::loaded::LoadedChunk) and
9/// [`UnloadedChunk`](super::unloaded::UnloadedChunk).
10pub trait Chunk {
11    /// Gets the height of this chunk in meters or blocks.
12    fn height(&self) -> u32;
13
14    /// Gets the block at the provided position in this chunk. `x` and `z`
15    /// are in the range `0..16` while `y` is in the range `0..height`.
16    ///
17    /// # Panics
18    ///
19    /// May panic if the position is out of bounds.
20    #[track_caller]
21    fn block(&self, x: u32, y: u32, z: u32) -> BlockRef {
22        BlockRef {
23            state: self.block_state(x, y, z),
24            nbt: self.block_entity(x, y, z),
25        }
26    }
27
28    /// Sets the block at the provided position in this chunk. `x` and `z`
29    /// are in the range `0..16` while `y` is in the range `0..height`. The
30    /// previous block at the position is returned.
31    ///
32    /// # Panics
33    ///
34    /// May panic if the position is out of bounds.
35    #[track_caller]
36    fn set_block(&mut self, x: u32, y: u32, z: u32, block: impl IntoBlock) -> Block {
37        let block = block.into_block();
38        let state = self.set_block_state(x, y, z, block.state);
39        let nbt = self.set_block_entity(x, y, z, block.nbt);
40
41        Block { state, nbt }
42    }
43
44    /// Sets all the blocks in the entire chunk to the provided block.
45    fn fill_blocks(&mut self, block: impl IntoBlock) {
46        let block = block.into_block();
47
48        self.fill_block_states(block.state);
49
50        if block.nbt.is_some() {
51            for x in 0..16 {
52                for z in 0..16 {
53                    for y in 0..self.height() {
54                        self.set_block_entity(x, y, z, block.nbt.clone());
55                    }
56                }
57            }
58        } else {
59            self.clear_block_entities();
60        }
61    }
62
63    /// Gets the block state at the provided position in this chunk. `x` and `z`
64    /// are in the range `0..16` while `y` is in the range `0..height`.
65    ///
66    /// # Panics
67    ///
68    /// May panic if the position is out of bounds.
69    #[track_caller]
70    fn block_state(&self, x: u32, y: u32, z: u32) -> BlockState;
71
72    /// Sets the block state at the provided position in this chunk. `x` and `z`
73    /// are in the range `0..16` while `y` is in the range `0..height`. The
74    /// previous block state at the position is returned.
75    ///
76    /// **NOTE:** This is a low-level function which may break expected
77    /// invariants for block entities. Prefer [`Self::set_block`] if performance
78    /// is not a concern.
79    ///
80    /// # Panics
81    ///
82    /// May panic if the position is out of bounds.
83    #[track_caller]
84    fn set_block_state(&mut self, x: u32, y: u32, z: u32, block: BlockState) -> BlockState;
85
86    /// Replaces all block states in the entire chunk with the provided block
87    /// state.
88    ///
89    /// **NOTE:** This is a low-level function which may break expected
90    /// invariants for block entities. Prefer [`Self::fill_blocks`] instead.
91    fn fill_block_states(&mut self, block: BlockState) {
92        for sect_y in 0..self.height() / 16 {
93            self.fill_block_state_section(sect_y, block);
94        }
95    }
96
97    /// Replaces all the block states in a section with the provided block
98    /// state.
99    ///
100    /// **NOTE:** This is a low-level function which may break expected
101    /// invariants for block entities. Prefer [`Self::set_block`] if performance
102    /// is not a concern.
103    ///
104    /// # Panics
105    ///
106    /// May panic if the section offset is out of bounds.
107    #[track_caller]
108    fn fill_block_state_section(&mut self, sect_y: u32, block: BlockState);
109
110    /// Gets the block entity at the provided position in this chunk. `x` and
111    /// `z` are in the range `0..16` while `y` is in the range `0..height`.
112    ///
113    /// # Panics
114    ///
115    /// May panic if the position is out of bounds.
116    #[track_caller]
117    fn block_entity(&self, x: u32, y: u32, z: u32) -> Option<&Compound>;
118
119    /// Gets a mutable reference to the block entity at the provided position in
120    /// this chunk. `x` and `z` are in the range `0..16` while `y` is in the
121    /// range `0..height`.
122    ///
123    /// # Panics
124    ///
125    /// May panic if the position is out of bounds.
126    #[track_caller]
127    fn block_entity_mut(&mut self, x: u32, y: u32, z: u32) -> Option<&mut Compound>;
128
129    /// Sets the block entity at the provided position in this chunk. `x` and
130    /// `z` are in the range `0..16` while `y` is in the range `0..height`.
131    /// The previous block entity at the position is returned.
132    ///
133    /// **NOTE:** This is a low-level function which may break expected
134    /// invariants for block entities. Prefer [`Self::set_block`] if performance
135    /// is not a concern.
136    ///
137    /// # Panics
138    ///
139    /// May panic if the position is out of bounds.
140    #[track_caller]
141    fn set_block_entity(
142        &mut self,
143        x: u32,
144        y: u32,
145        z: u32,
146        block_entity: Option<Compound>,
147    ) -> Option<Compound>;
148
149    /// Removes all block entities from the chunk.
150    ///
151    /// **NOTE:** This is a low-level function which may break expected
152    /// invariants for block entities. Prefer [`Self::set_block`] if performance
153    /// is not a concern.
154    fn clear_block_entities(&mut self);
155
156    /// Gets the biome at the provided position in this chunk. `x` and `z` are
157    /// in the range `0..4` while `y` is in the range `0..height / 4`.
158    ///
159    /// Note that biomes are 4x4x4 segments of a chunk, so the xyz arguments to
160    /// this method differ from those to [`Self::block_state`] and
161    /// [`Self::block_entity`].
162    ///
163    /// # Panics
164    ///
165    /// May panic if the position is out of bounds.
166    #[track_caller]
167    fn biome(&self, x: u32, y: u32, z: u32) -> BiomeId;
168
169    /// Sets the biome at the provided position in this chunk. The Previous
170    /// biome at the position is returned. `x` and `z` are in the range `0..4`
171    /// while `y` is in the range `0..height / 4`.
172    ///
173    /// Note that biomes are 4x4x4 segments of a chunk, so the xyz arguments to
174    /// this method differ from those to [`Self::block_state`] and
175    /// [`Self::block_entity`].
176    ///
177    /// # Panics
178    ///
179    /// May panic if the position is out of bounds.
180    #[track_caller]
181    fn set_biome(&mut self, x: u32, y: u32, z: u32, biome: BiomeId) -> BiomeId;
182
183    /// Sets all the biomes in the entire chunk to the provided biome.
184    fn fill_biomes(&mut self, biome: BiomeId) {
185        for sect_y in 0..self.height() / 16 {
186            self.fill_biome_section(sect_y, biome);
187        }
188    }
189
190    /// Replaces all the biomes in a section with the provided biome.
191    ///
192    /// # Panics
193    ///
194    /// May panic if the section offset is out of bounds.
195    #[track_caller]
196    fn fill_biome_section(&mut self, sect_y: u32, biome: BiomeId);
197
198    /// Sets all blocks and biomes in this chunk to the default values. The
199    /// height of the chunk is not modified.
200    fn clear(&mut self) {
201        self.fill_block_states(BlockState::AIR);
202        self.fill_biomes(BiomeId::default());
203        self.clear_block_entities();
204    }
205
206    /// Attempts to optimize this chunk by reducing its memory usage or other
207    /// characteristics. This may be a relatively expensive operation.
208    ///
209    /// This method must not alter the semantics of the chunk in any observable
210    /// way.
211    fn shrink_to_fit(&mut self);
212}
213
214/// Represents a complete block, which is a pair of block state and optional NBT
215/// data for the block entity.
216#[derive(Clone, PartialEq, Default, Debug)]
217pub struct Block {
218    pub state: BlockState,
219    pub nbt: Option<Compound>,
220}
221
222impl Block {
223    pub const fn new(state: BlockState, nbt: Option<Compound>) -> Self {
224        Self { state, nbt }
225    }
226}
227
228/// Like [`Block`], but immutably referenced.
229#[derive(Copy, Clone, PartialEq, Default, Debug)]
230pub struct BlockRef<'a> {
231    pub state: BlockState,
232    pub nbt: Option<&'a Compound>,
233}
234
235impl<'a> BlockRef<'a> {
236    pub const fn new(state: BlockState, nbt: Option<&'a Compound>) -> Self {
237        Self { state, nbt }
238    }
239}
240
241pub trait IntoBlock {
242    // TODO: parameterize this with block registry ref?
243    fn into_block(self) -> Block;
244}
245
246impl IntoBlock for Block {
247    fn into_block(self) -> Block {
248        self
249    }
250}
251
252impl IntoBlock for BlockRef<'_> {
253    fn into_block(self) -> Block {
254        Block {
255            state: self.state,
256            nbt: self.nbt.cloned(),
257        }
258    }
259}
260
261/// This will initialize the block with a new empty compound if the block state
262/// is associated with a block entity.
263impl IntoBlock for BlockState {
264    fn into_block(self) -> Block {
265        Block {
266            state: self,
267            nbt: self.block_entity_kind().map(|_| Compound::new()),
268        }
269    }
270}
271
272pub(super) const SECTION_BLOCK_COUNT: usize = 16 * 16 * 16;
273pub(super) const SECTION_BIOME_COUNT: usize = 4 * 4 * 4;
274
275/// The maximum height of a chunk.
276pub const MAX_HEIGHT: u32 = 4096;
277
278pub(super) type BlockStateContainer =
279    PalettedContainer<BlockState, SECTION_BLOCK_COUNT, { SECTION_BLOCK_COUNT / 2 }>;
280
281pub(super) type BiomeContainer =
282    PalettedContainer<BiomeId, SECTION_BIOME_COUNT, { SECTION_BIOME_COUNT / 2 }>;
283
284#[inline]
285#[track_caller]
286pub(super) fn check_block_oob(chunk: &impl Chunk, x: u32, y: u32, z: u32) {
287    assert!(
288        x < 16 && y < chunk.height() && z < 16,
289        "chunk block offsets of ({x}, {y}, {z}) are out of bounds"
290    );
291}
292
293#[inline]
294#[track_caller]
295pub(super) fn check_biome_oob(chunk: &impl Chunk, x: u32, y: u32, z: u32) {
296    assert!(
297        x < 4 && y < chunk.height() / 4 && z < 4,
298        "chunk biome offsets of ({x}, {y}, {z}) are out of bounds"
299    );
300}
301
302#[inline]
303#[track_caller]
304pub(super) fn check_section_oob(chunk: &impl Chunk, sect_y: u32) {
305    assert!(
306        sect_y < chunk.height() / 16,
307        "chunk section offset of {sect_y} is out of bounds"
308    );
309}
310
311/// Returns the minimum number of bits needed to represent the integer `n`.
312pub(super) const fn bit_width(n: usize) -> usize {
313    (usize::BITS - n.leading_zeros()) as usize
314}
315
316#[cfg(test)]
317mod tests {
318    use super::*;
319    use crate::layer::chunk::{LoadedChunk, UnloadedChunk};
320
321    #[test]
322    fn chunk_get_set() {
323        fn check(mut chunk: impl Chunk) {
324            assert_eq!(
325                chunk.set_block_state(1, 2, 3, BlockState::CHAIN),
326                BlockState::AIR
327            );
328            assert_eq!(
329                chunk.set_block_state(1, 2, 3, BlockState::AIR),
330                BlockState::CHAIN
331            );
332
333            assert_eq!(chunk.set_block_entity(1, 2, 3, Some(Compound::new())), None);
334            assert_eq!(chunk.set_block_entity(1, 2, 3, None), Some(Compound::new()));
335        }
336
337        let unloaded = UnloadedChunk::with_height(512);
338        let loaded = LoadedChunk::new(512);
339
340        check(unloaded);
341        check(loaded);
342    }
343
344    #[cfg(debug_assertions)]
345    #[test]
346    #[should_panic]
347    fn chunk_debug_oob_0() {
348        let mut chunk = UnloadedChunk::with_height(512);
349        chunk.set_block_state(0, 0, 16, BlockState::AIR);
350    }
351
352    #[cfg(debug_assertions)]
353    #[test]
354    #[should_panic]
355    fn chunk_debug_oob_1() {
356        let mut chunk = LoadedChunk::new(512);
357        chunk.set_block_state(0, 0, 16, BlockState::AIR);
358    }
359
360    #[cfg(debug_assertions)]
361    #[test]
362    #[should_panic]
363    fn chunk_debug_oob_2() {
364        let mut chunk = UnloadedChunk::with_height(512);
365        chunk.set_block_entity(0, 0, 16, None);
366    }
367
368    #[cfg(debug_assertions)]
369    #[test]
370    #[should_panic]
371    fn chunk_debug_oob_3() {
372        let mut chunk = LoadedChunk::new(512);
373        chunk.set_block_entity(0, 0, 16, None);
374    }
375
376    #[cfg(debug_assertions)]
377    #[test]
378    #[should_panic]
379    fn chunk_debug_oob_4() {
380        let mut chunk = UnloadedChunk::with_height(512);
381        chunk.set_biome(0, 0, 4, BiomeId::DEFAULT);
382    }
383
384    #[cfg(debug_assertions)]
385    #[test]
386    #[should_panic]
387    fn chunk_debug_oob_5() {
388        let mut chunk = LoadedChunk::new(512);
389        chunk.set_biome(0, 0, 4, BiomeId::DEFAULT);
390    }
391
392    #[cfg(debug_assertions)]
393    #[test]
394    #[should_panic]
395    fn chunk_debug_oob_6() {
396        let mut chunk = UnloadedChunk::with_height(512);
397        chunk.fill_block_state_section(chunk.height() / 16, BlockState::AIR);
398    }
399
400    #[cfg(debug_assertions)]
401    #[test]
402    #[should_panic]
403    fn chunk_debug_oob_7() {
404        let mut chunk = LoadedChunk::new(512);
405        chunk.fill_block_state_section(chunk.height() / 16, BlockState::AIR);
406    }
407
408    #[cfg(debug_assertions)]
409    #[test]
410    #[should_panic]
411    fn chunk_debug_oob_8() {
412        let mut chunk = UnloadedChunk::with_height(512);
413        chunk.fill_biome_section(chunk.height() / 16, BiomeId::DEFAULT);
414    }
415
416    #[cfg(debug_assertions)]
417    #[test]
418    #[should_panic]
419    fn chunk_debug_oob_9() {
420        let mut chunk = LoadedChunk::new(512);
421        chunk.fill_biome_section(chunk.height() / 16, BiomeId::DEFAULT);
422    }
423}