mc_core/world/
chunk.rs

1use std::collections::HashSet;
2use std::time::Instant;
3use std::sync::Arc;
4
5use thiserror::Error;
6use hecs::Entity;
7
8use crate::util::{PackedArray, Palette, Rect, cast_vec_ref_to_ptr};
9use crate::heightmap::HeightmapType;
10use crate::block::BlockState;
11use crate::biome::Biome;
12use crate::perf;
13
14use super::level::LevelEnv;
15
16
17/// The number of blocks for each direction in sub chunks.
18pub const SIZE: usize = 16;
19/// The total count of data for a 3 dimensional cube of `SIZE`.
20pub const BLOCKS_DATA_SIZE: usize = SIZE * SIZE * SIZE;
21/// The total count of biomes samples for 3 dimensional biomes.
22pub const BIOMES_DATA_SIZE: usize = 4 * 4 * 4;
23
24
25/// Internal height map used for optimization of the heightmap computation, it allows to ignore
26/// useless null blocks (usually air).
27static HEIGHTMAP_NON_NULL: HeightmapType = HeightmapType {
28    name: "INTERNAL_NON_NULL",
29    predicate: |state, blocks| blocks.get_state_from(0).unwrap() != state
30};
31
32
33/// An error enumeration for all operations related to blocks and biomes on
34/// sub chunks and chunks. Check each variation for specific documentation.
35#[derive(Error, Debug)]
36pub enum ChunkError {
37    #[error("You are trying to alter a sub chunk that is out of the height range.")]
38    SubChunkOutOfRange,
39    #[error("You are trying get information from an unloaded sub chunk.")]
40    SubChunkUnloaded,
41    #[error("You gave a block reference that is not supported by this chunk's level's env.")]
42    IllegalBlock,
43    #[error("You gave a biome reference that is not supported by this chunk's level's env.")]
44    IllegalBiome,
45    #[error("You gave a heightmap type that is not supported by this chunk's level's env.")]
46    IllegalHeightmap,
47    #[error("You are trying to access an unloaded chunk.")]
48    ChunkUnloaded,  // Made for LevelStorage
49}
50
51/// A type alias with an error type of `ChunkError`.
52pub type ChunkResult<T> = Result<T, ChunkError>;
53
54
55/// An enumeration for the different generation status used by the game. Depending on the
56/// generation algorithm some status might not be used.
57#[derive(Debug, Copy, Clone)]
58pub enum ChunkStatus {
59    Empty,
60    StructureStarts,
61    StructureReferences,
62    Biomes,
63    Noise,
64    Surface,
65    Carvers,
66    LiquidCarvers,
67    Features,
68    Light,
69    Spawn,
70    Heightmaps,
71    Full
72}
73
74
75/// A vertical chunk, 16x16 blocks. This vertical chunk is composed of multiple `SubChunk`s,
76/// each sub chunk has stores blocks, biomes, lights. This structure however stores metadata
77/// about the chunk like inhabited time and generation status. All entities and block entities
78/// are also stored in the chunk.
79pub struct Chunk {
80    /// Chunk X position.
81    cx: i32,
82    /// Chunk Z position.
83    cz: i32,
84    /// The level environment with block and biomes global palettes.
85    env: Arc<LevelEnv>,
86    /// The array of sub chunks, defined once with a specific size depending on the height.
87    sub_chunks: Vec<Option<SubChunk>>,
88    /// The offset of the lower chunk.
89    sub_chunks_offset: i8,
90    /// Modern cube biomes array, this array does not use any palette since the global palette
91    /// should be small enough to only take 7 to 8 bits per point. Since there 64 biomes in
92    /// a sub chunk, this make only 64 octets to store a sub chunk.
93    ///
94    /// Biomes are stored separately from sub chunks because sub chunks are not necessarily
95    /// existing for empty sub chunks even at valid Y positions. But biomes must always be
96    /// defined for all the height.
97    biomes: PackedArray,
98    /// Array containing data for all registered heightmaps in the level environment. For memory
99    /// efficiency we store all our heightmaps in the same packed array.
100    heightmaps: PackedArray,
101    /// The current generation status of this chunk.
102    status: ChunkStatus,
103    /// Total number of ticks players has been in this chunk, this increase faster when
104    /// more players are in the chunk.
105    inhabited_time: u64,
106    /// A list of entity handles that are located in this vertical chunk.
107    entities: HashSet<Entity>,
108    /// Last save instant.
109    last_save: Instant
110}
111
112impl Chunk {
113
114    pub(super) fn new(env: Arc<LevelEnv>, height: ChunkHeight, cx: i32, cz: i32) -> Self {
115
116        // This is 7 bits for current vanilla biomes, this only take 64 octets of storage.
117        // Subtracting 1 because the maximum biome save ID is the maximum value.
118        let biomes_byte_size = PackedArray::calc_min_byte_size(env.biomes.biomes_count() as u64 - 1);
119
120        let heightmap_byte_size = PackedArray::calc_min_byte_size((height.len() * 16) as u64);
121        // Added 256 for the additional internal non null heightmap HEIGHTMAP_NON_NULL.
122        let heightmap_len = env.heightmaps.heightmaps_count() * 256 + 256;
123
124        Chunk {
125            cx,
126            cz,
127            env,
128            status: ChunkStatus::Empty,
129            sub_chunks: (0..height.len()).map(|_| None).collect(),
130            sub_chunks_offset: height.min,
131            biomes: PackedArray::new(height.len() * 64, biomes_byte_size, None),
132            heightmaps: PackedArray::new(heightmap_len, heightmap_byte_size, None),
133            inhabited_time: 0,
134            entities: HashSet::new(),
135            last_save: Instant::now()
136        }
137
138    }
139
140    /// Get the chunk position `(x, z)`.
141    #[inline]
142    pub fn get_position(&self) -> (i32, i32) {
143        (self.cx, self.cz)
144    }
145
146    #[inline]
147    pub fn get_env(&self) -> &Arc<LevelEnv> {
148        &self.env
149    }
150
151    #[inline]
152    pub fn get_status(&self) -> ChunkStatus {
153        self.status
154    }
155
156    #[inline]
157    pub fn set_status(&mut self, status: ChunkStatus) {
158        self.status = status;
159    }
160
161    #[inline]
162    pub fn get_inhabited_time(&self) -> u64 {
163        self.inhabited_time
164    }
165
166    #[inline]
167    pub fn set_inhabited_time(&mut self, time: u64) {
168        self.inhabited_time = time;
169    }
170
171    #[inline]
172    pub fn get_last_save(&self) -> Instant {
173        self.last_save
174    }
175
176    #[inline]
177    pub fn update_last_save(&mut self) {
178        self.last_save = Instant::now();
179    }
180
181    // Height //
182
183    /// Return the configured height for the level owning this chunk.
184    #[inline]
185    pub fn get_height(&self) -> ChunkHeight {
186        ChunkHeight {
187            min: self.sub_chunks_offset,
188            max: self.sub_chunks_offset + self.sub_chunks.len() as i8 - 1
189        }
190    }
191
192    /// Return the linear non-negative offset of the chunk at the given position,
193    /// the position can be negative. None is returned if overflow occurred.
194    ///
195    /// As its name implies, this method only calculate the offset, the returned
196    /// offset might be out of the chunk's height. **It is made for internal use.**
197    fn calc_sub_chunk_offset(&self, cy: i8) -> Option<usize> {
198        cy.checked_sub(self.sub_chunks_offset).map(|v| v as usize)
199    }
200
201    /// Return the linear non-negative offset of the chunk at the given position,
202    /// the position can be negative. None is returned if the chunk position is
203    /// not within the chunk's height.
204    pub fn get_sub_chunk_offset(&self, cy: i8) -> Option<usize> {
205        match self.calc_sub_chunk_offset(cy) {
206            Some(off) if off < self.sub_chunks.len() => Some(off),
207            _ => None
208        }
209    }
210
211    // Sub chunks //
212
213    /// Ensure that a sub chunk is existing at a specific chunk-Y coordinate, if this coordinate
214    /// is out of the height of the level, `Err(ChunkError::SubChunkOutOfRange)` is returned.
215    pub fn ensure_sub_chunk(&mut self, cy: i8) -> ChunkResult<&mut SubChunk> {
216
217        let offset = self.calc_sub_chunk_offset(cy).ok_or(ChunkError::SubChunkOutOfRange)?;
218
219        match self.sub_chunks.get_mut(offset) {
220            Some(Some(sub_chunk)) => Ok(sub_chunk),
221            Some(sub_chunk ) => {
222                Ok(sub_chunk.insert(SubChunk::new(Arc::clone(&self.env))))
223            },
224            None => Err(ChunkError::SubChunkOutOfRange)
225        }
226
227    }
228
229    #[inline]
230    pub fn ensure_sub_chunk_at(&mut self, y: i32) -> ChunkResult<&mut SubChunk> {
231        self.ensure_sub_chunk((y >> 4) as i8)
232    }
233
234    /// Replace the sub chunk at the given Y chunk coordinate by the given one.
235    ///
236    /// # Panics (debug only):
237    /// The method panics if the given sub chunk has not the same sub chunk environment as self.
238    pub fn replace_sub_chunk(&mut self, cy: i8, sub_chunk: SubChunk) -> ChunkResult<&mut SubChunk> {
239        debug_assert!(Arc::ptr_eq(&self.env, &sub_chunk.env));
240        let offset = self.calc_sub_chunk_offset(cy).ok_or(ChunkError::SubChunkOutOfRange)?;
241        let container = self.sub_chunks.get_mut(offset).ok_or(ChunkError::SubChunkOutOfRange)?;
242        Ok(container.insert(sub_chunk))
243    }
244
245    /// Get a sub chunk reference at a specified index.
246    pub fn get_sub_chunk(&self, cy: i8) -> Option<&SubChunk> {
247        let offset = self.calc_sub_chunk_offset(cy)?;
248        match self.sub_chunks.get(offset) {
249            Some(Some(chunk)) => Some(chunk),
250            _ => None
251        }
252    }
253
254    #[inline]
255    pub fn get_sub_chunk_at(&self, y: i32) -> Option<&SubChunk> {
256        self.get_sub_chunk((y >> 4) as i8)
257    }
258
259    /// Get a sub chunk mutable reference at a specified index.
260    pub fn get_sub_chunk_mut(&mut self, cy: i8) -> Option<&mut SubChunk> {
261        let offset = self.calc_sub_chunk_offset(cy)?;
262        match self.sub_chunks.get_mut(offset) {
263            Some(Some(chunk)) => Some(chunk),
264            _ => None
265        }
266    }
267
268    #[inline]
269    pub fn get_sub_chunk_at_mut(&mut self, y: i32) -> Option<&mut SubChunk> {
270        self.get_sub_chunk_mut((y >> 4) as i8)
271    }
272
273
274    /// Return the number of sub chunks in the height of this chunk.
275    pub fn get_sub_chunks_count(&self) -> usize {
276        self.sub_chunks.len()
277    }
278
279    /// Iterator over all sub chunks with their Y coordinate, sub chunks may be not loaded (`None`).
280    pub fn iter_sub_chunks(&self) -> impl Iterator<Item = (i8, Option<&'_ SubChunk>)> + '_ {
281        let min_y = self.sub_chunks_offset;
282        self.sub_chunks.iter()
283            .enumerate()
284            .map(move |(idx, opt)| {
285                (idx as i8 + min_y, opt.as_ref())
286            })
287    }
288
289    /// Iterator only over loaded sub chunks.
290    pub fn iter_loaded_sub_chunks(&self) -> impl Iterator<Item = (i8, &'_ SubChunk)> + '_ {
291        let min_y = self.sub_chunks_offset;
292        self.sub_chunks.iter()
293            .enumerate()
294            .filter_map(move |(idx, opt)| {
295                opt.as_ref().map(move |sc| (idx as i8 + min_y, sc))
296            })
297    }
298
299    /// Return the index of the first sub chunk (starting from the top sub chunk) that is loaded
300    /// AND contains a non null block.
301    pub fn get_highest_non_null_sub_chunk(&self) -> i8 {
302        self.sub_chunks.iter()
303            .rposition(|sc| {
304                match sc {
305                    Some(sc) => sc.has_non_null_block(),
306                    None => false
307                }
308            }).unwrap_or(0) as i8 + self.sub_chunks_offset
309    }
310
311    // BLOCKS //
312
313    /// Get the block at a specific position.
314    ///
315    /// Returns `Ok(&BlockState)` if the block is found and valid,
316    /// `Err(ChunkError::SubChunkOutOfRange)` if the given y coordinate is out of the chunk
317    /// height, if the Y coordinate is valid but the sub chunk is yet loaded, the "null-block"
318    /// is returned.
319    ///
320    /// # Panics (debug-only)
321    /// This method panics if either X or Z is higher than 15.
322    pub fn get_block(&self, x: u8, y: i32, z: u8) -> ChunkResult<&'static BlockState> {
323
324        let offset = self.calc_sub_chunk_offset((y >> 4) as i8)
325            .ok_or(ChunkError::SubChunkOutOfRange)?;
326
327        match self.sub_chunks.get(offset) {
328            Some(Some(sub_chunk)) => Ok(sub_chunk.get_block(x, (y & 15) as u8, z)),
329            Some(None) => Ok(self.env.blocks.get_state_from(0).unwrap()),
330            _ => Err(ChunkError::SubChunkOutOfRange)
331        }
332
333
334        /*match self.get_sub_chunk((y >> 4) as i8) {
335            None => Err(ChunkError::SubChunkUnloaded),
336            Some(sub_chunk) => Ok(sub_chunk.get_block(x, (y & 15) as u8, z))
337        }*/
338
339    }
340
341    /// Same description as `get_block` but accept level coordinates instead of relative ones. This
342    /// method ignore if the given coordinates are not actually pointing to this chunk, it only
343    /// take the 4 least significant bits.
344    #[inline]
345    pub fn get_block_at(&self, x: i32, y: i32, z: i32) -> ChunkResult<&'static BlockState> {
346        self.get_block((x & 15) as u8, y, (z & 15) as u8)
347    }
348
349    /// Set the block at a specific position relative to this chunk.
350    ///
351    /// Return `Ok(())` if the biome was successfully set, `Err(ChunkError::SubChunkOutOfRange)` if
352    /// the given Y coordinate is invalid for the level or `Err(ChunkError::IllegalBlock)` if the
353    /// given block state is not registered in the current world.
354    ///
355    /// # Panics (debug-only)
356    /// This method panics if either X or Z is higher than 15.
357    pub fn set_block(&mut self, x: u8, y: i32, z: u8, state: &'static BlockState) -> ChunkResult<()> {
358        let sub_chunk = self.ensure_sub_chunk_at(y)?;
359        match sub_chunk.set_block(x, (y & 15) as u8, z, state) {
360            Ok(()) => {
361                self.update_heightmap_column(x, y, z, state);
362                Ok(())
363            },
364            e => e
365        }
366    }
367
368    /// Same description as `set_block` but accept level coordinates instead of relative ones. This
369    /// method ignore if the given coordinates are not actually pointing to this chunk, it only
370    /// take the 4 least significant bits.
371    #[inline]
372    pub fn set_block_at(&mut self, x: i32, y: i32, z: i32, state: &'static BlockState) -> ChunkResult<()> {
373        self.set_block((x & 15) as u8, y, (z & 15) as u8, state)
374    }
375
376    // BIOMES //
377
378    fn calc_biome_offset(&self, x: u8, y: i32, z: u8) -> usize {
379        calc_biome_index(x, (y - self.sub_chunks_offset as i32 * 4) as usize, z)
380    }
381
382    /// Get a biome at specific biome coordinates, biome coordinates are different from block
383    /// coordinates because there is only 4x4x4 biomes samples for each sub chunk.
384    /// Given X and Z coordinates must be lower than 4 and given Y coordinate must be valid
385    /// for the chunk height.
386    ///
387    /// Returns `Ok(biome)` or `Err(ChunkError::SubChunkOutOfRange)` if the given Y coordinate
388    /// is not supported by this chunk's height.
389    ///
390    /// # Panics (debug-only)
391    /// This method panics if either X or Z is higher than 3.
392    pub fn get_biome(&self, x: u8, y: i32, z: u8) -> ChunkResult<&'static Biome> {
393        let offset = self.calc_biome_offset(x, y, z);
394        let sid = self.biomes.get(offset).ok_or(ChunkError::SubChunkOutOfRange)? as u16;
395        Ok(self.env.biomes.get_biome_from(sid).unwrap())
396    }
397
398    #[inline]
399    pub fn get_biome_at(&self, x: i32, y: i32, z: i32) -> ChunkResult<&'static Biome> {
400        self.get_biome(((x >> 2) & 3) as u8, y >> 2, ((z >> 2) & 3) as u8)
401    }
402
403    /// Get a biome at specific biome coordinates, biome coordinates are different from block
404    /// coordinates because there is only 4x4x4 biomes samples for each sub chunk.
405    /// Given X and Z coordinates must be lower than 4 and given Y coordinate must be valid
406    /// for the chunk height.
407    ///
408    /// Returns `Ok(biome)` or `Err(ChunkError::SubChunkOutOfRange)` if the given Y coordinate
409    /// is not supported by this chunk's height.
410    ///
411    /// # Panics (debug-only)
412    /// This method panics if either X or Z is higher than 3.
413    pub fn set_biome(&mut self, x: u8, y: i32, z: u8, biome: &'static Biome) -> ChunkResult<()> {
414        let offset = self.calc_biome_offset(x, y, z);
415        let sid = self.env.biomes.get_sid_from(biome).ok_or(ChunkError::IllegalBiome)?;
416        if offset < self.biomes.len() {
417            self.biomes.set(offset, sid as u64);
418            Ok(())
419        } else {
420            Err(ChunkError::SubChunkOutOfRange)
421        }
422    }
423
424    #[inline]
425    pub fn set_biome_at(&mut self, x: i32, y: i32, z: i32, biome: &'static Biome) -> ChunkResult<()> {
426        self.set_biome(((x >> 2) & 3) as u8, y >> 2, ((z >> 2) & 3) as u8, biome)
427    }
428
429    /// Set all biome quads in this chunk according a to a 2D biomes rectangle (16x16).
430    /// The implementation currently take the lower coordinates biome in each 4x4 rectangle
431    /// and set it to the whole chunk height.
432    pub fn set_biomes_2d(&mut self, biomes: &Rect<&'static Biome>) -> ChunkResult<()> {
433
434        assert!(biomes.x_size >= 16 && biomes.z_size >= 16, "Given biomes rectangle is too small.");
435
436        let mut layer_biomes = [0; 16];
437
438        for z in 0..4 {
439            for x in 0..4 {
440                let idx = x + z * 4;
441                let biome = biomes.data[idx * 4];
442                layer_biomes[idx] = self.env.biomes.get_sid_from(biome).ok_or(ChunkError::IllegalBiome)?;
443            }
444        }
445
446        self.biomes.replace(move |i, _| layer_biomes[i % 16] as u64);
447        Ok(())
448
449    }
450
451    pub fn set_biomes_3d(&mut self, biomes: &[&'static Biome]) -> ChunkResult<()> {
452        assert_eq!(biomes.len(), self.sub_chunks.len() * 64, "Given biomes array must be {} biomes long.", self.sub_chunks.len() * 64);
453        let env_biomes = &self.env.biomes;
454        self.biomes.replace(move |i, old| {
455            env_biomes.get_sid_from(biomes[i]).map(|v| v as u64).unwrap_or(old)
456        });
457        Ok(())
458    }
459
460    pub fn get_biomes_count(&self) -> usize {
461        self.biomes.len()
462    }
463
464    /// Expose internal biomes storage, retuning an iterator with each biomes in this chunk,
465    /// ordered by X, Z then Y.
466    pub fn iter_biomes(&self) -> impl Iterator<Item = &'static Biome> + '_ {
467        let biomes = &self.env.biomes;
468        self.biomes.iter().map(move |v| biomes.get_biome_from(v as u16).unwrap())
469    }
470
471    // HEIGHTMAPS //
472
473    /// Internal method that returns the offset of a given heightmap, if the heightmap is not
474    /// known by the environment's heightmaps, `Err(ChunkError::IllegalHeightmap)` is returned.
475    fn get_heightmap_column_index(&self, heightmap_type: &'static HeightmapType, x: u8, z: u8) -> ChunkResult<usize> {
476        self.env.heightmaps.get_heightmap_index(heightmap_type)
477            .map(move |offset| 256 + offset * 256 + calc_heightmap_index(x, z))
478            .ok_or(ChunkError::IllegalHeightmap)
479    }
480
481    /// Set the value of a specific heightmap at specific coordinates. This is very unsafe to do
482    /// that manually, you should ensure that the condition of the given heightmap type are kept.
483    pub fn set_heightmap_column(&mut self, heightmap_type: &'static HeightmapType, x: u8, z: u8, y: i32) -> ChunkResult<()> {
484        let column_index = self.get_heightmap_column_index(heightmap_type, x, z)?;
485        self.heightmaps.set(column_index, (y - self.get_height().get_min_block()) as u64);
486        Ok(())
487    }
488
489    #[inline]
490    pub fn set_heightmap_column_at(&mut self, heightmap_type: &'static HeightmapType, x: i32, z: i32, y: i32) -> ChunkResult<()> {
491        self.set_heightmap_column(heightmap_type, (x & 15) as u8, (z & 15) as u8, y)
492    }
493
494    /// Get the value of a specific heightmap at specific coordinates.
495    pub fn get_heightmap_column(&self, heightmap_type: &'static HeightmapType, x: u8, z: u8) -> ChunkResult<i32> {
496        let column_index = self.get_heightmap_column_index(heightmap_type, x, z)?;
497        // SAFETY: We unwrap because the column index is checked into `get_heightmap_column_index`
498        //         and if x or z are wrong, this panics in debug mode.
499        Ok(self.heightmaps.get(column_index).unwrap() as i32 + self.get_height().get_min_block())
500    }
501
502    #[inline]
503    pub fn get_heightmap_column_at(&self, heightmap_type: &'static HeightmapType, x: i32, z: i32) -> ChunkResult<i32> {
504        self.get_heightmap_column(heightmap_type, (x & 15) as u8, (z & 15) as u8)
505    }
506
507    /// Efficiently update all heightmaps for a specific column according to a block update.
508    /// If you don't have a state change, you can use `recompute_heightmap_column` instead.
509    fn update_heightmap_column(&mut self, x: u8, y: i32, z: u8, state: &'static BlockState) {
510
511        let min_block_y = self.get_height().get_min_block();
512        let column_index = calc_heightmap_index(x, z);
513
514        for (idx, heightmap_type) in std::iter::once(&HEIGHTMAP_NON_NULL)
515            .chain(self.env.heightmaps.iter_heightmap_types())
516            .enumerate()
517        {
518            let column_index = idx * 256 + column_index;
519            let current_y = self.heightmaps.get(column_index).unwrap() as i32 + min_block_y;
520            if heightmap_type.check_block(state, &self.env.blocks) {
521                // For example, if we have `current_y = 0`, `y = 0`, then we set `1`.
522                if y >= current_y {
523                    self.heightmaps.set(column_index, (y + 1 - min_block_y) as u64);
524                }
525            } else {
526                // For example, if we have `current_y = 2`, `y = 1`, then we recompute.
527                // But if we have `y = 0`, then we do nothing because top block is unchanged.
528                if y + 1 == current_y {
529                    let height = self.recompute_heightmap_column_internal(heightmap_type, x, z, y - 1);
530                    self.heightmaps.set(column_index, height);
531                }
532            }
533        }
534
535    }
536
537    /// Public method that you can use to recompute a single column for all heightmaps.
538    pub fn recompute_heightmap_column(&mut self, x: u8, z: u8) {
539
540        perf::push("Chunk::recompute_heightmap_column");
541
542        let min_block_y = self.get_height().get_min_block();
543        let max_block_y = self.get_height().get_max_block();
544        let column_index = calc_heightmap_index(x, z);
545
546        perf::push("non_null_heightmap");
547        let non_null_height = self.recompute_heightmap_column_internal(&HEIGHTMAP_NON_NULL, x, z, max_block_y);
548        self.heightmaps.set(column_index, non_null_height);
549        perf::pop();
550
551        // Let's recompute each heightmap only from this y coordinate.
552        let from_y = non_null_height as i32 + min_block_y - 1;
553
554        for (idx, heightmap_type) in self.env.heightmaps.iter_heightmap_types().enumerate() {
555            perf::push("heightmap_type");
556            let column_index = 256 + idx * 256 + column_index;
557            let height = self.recompute_heightmap_column_internal(heightmap_type, x, z, from_y);
558            self.heightmaps.set(column_index, height);
559            perf::pop();
560        }
561
562        perf::pop();
563
564    }
565
566    /// Internal method that recompute a single column for a specific type of heightmap.
567    /// **You must ensure that given coordinates are valid for this chunk, the only value
568    /// that is allowed out of bounds is if the `from_y` value is lower than minimum.**
569    fn recompute_heightmap_column_internal(&self, heightmap_type: &'static HeightmapType, x: u8, z: u8, from_y: i32) -> u64 {
570
571        perf::push("Chunk::recompute_heightmap_column_internal");
572
573        let sub_chunk_index = (from_y >> 4) as i8 - self.sub_chunks_offset;
574        if sub_chunk_index < 0 {
575            perf::pop();
576            return 0; // If the given 'from_y' is out of bounds.
577        }
578
579        let mut sub_chunk_index = sub_chunk_index as usize;
580        let mut by = (from_y & 15) as u8 + 1;
581
582        loop {
583
584            let sub_chunk = loop {
585                match self.sub_chunks[sub_chunk_index] {
586                    Some(ref sub_chunk) if sub_chunk.has_non_null_block() => break sub_chunk,
587                    _ => {
588                        if sub_chunk_index == 0 {
589                            perf::pop();
590                            return 0; // If the lowest sub chunk is absent.
591                        } else {
592                            sub_chunk_index -= 1;
593                        }
594                    }
595                }
596            };
597
598            while by > 0 {
599                by -= 1;
600                perf::push("check_block");
601                let state = sub_chunk.get_block(x, by, z);
602                if heightmap_type.check_block(state, &self.env.blocks) {
603                    perf::pop();
604                    perf::pop();
605                    return (sub_chunk_index << 4) as u64 + by as u64 + 1;
606                }
607                perf::pop();
608            }
609
610            if sub_chunk_index == 0 {
611                perf::pop();
612                return 0; // We reached the lowest sub chunk.
613            } else {
614                sub_chunk_index -= 1;
615                by = 16;
616            }
617
618        }
619
620    }
621
622    /// Direct access method to internal packed array, returning each of the 256 values from the
623    /// given heightmap type if it exists, ordered by X then Z.
624    pub fn iter_heightmap_raw_columns(&self, heightmap_type: &'static HeightmapType) -> Option<(u8, impl Iterator<Item = u64> + '_)> {
625        let offset = 256 + self.env.heightmaps.get_heightmap_index(heightmap_type)? * 256;
626        Some((self.heightmaps.byte_size(), self.heightmaps.iter().skip(offset).take(256)))
627    }
628
629    // ENTITIES //
630
631    #[inline]
632    pub unsafe fn add_entity_unchecked(&mut self, entity: Entity) {
633        self.entities.insert(entity);
634    }
635
636    #[inline]
637    pub unsafe fn remove_entity_unchecked(&mut self, entity: Entity) {
638        self.entities.remove(&entity);
639    }
640
641    #[inline]
642    pub fn has_entity(&self, entity: Entity) -> bool {
643        self.entities.contains(&entity)
644    }
645
646}
647
648
649/// Internal sub chunks blocks palette.
650enum SubChunkBlocks {
651    /// Local blocks palette.
652    Local {
653        /// Blocks palette. It is limited to 128 blocks in order to support most blocks in a natural
654        /// generation, which is the case of most of the sub chunks that are untouched by players.
655        /// In case of "artificial chunks" made by players, the block palette is likely to overflow
656        /// the 128 block states limit, in this case it switch to the global palette (`GlobalBlocks`
657        /// in the level environment).
658        /// The palette uses a raw pointer in order to use the pointer equality instead of value eq.
659        palette: Palette<*const BlockState>,
660        /// Save ID of the null block in the palette. Set to `u32::MAX` if the null block is not
661        /// present in the local palette. This value is safe because the palette has a limited
662        /// capacity of `BLOCKS_PALETTE_CAPACITY` block states.
663        null_block_sid: u32
664    },
665    /// Global blocks palette.
666    Global
667}
668
669impl SubChunkBlocks {
670
671    /// Compute and return the effective null block save ID from the enum variant.
672    fn get_null_block_sid(&self) -> Option<u32> {
673        match self {
674            Self::Local { null_block_sid: u32::MAX, .. } => None,
675            Self::Local { null_block_sid, .. } => Some(*null_block_sid),
676            Self::Global => Some(0)
677        }
678    }
679
680}
681
682// Implemented because palette of '*const BlockState' prevent it by default.
683unsafe impl Send for SubChunkBlocks {}
684unsafe impl Sync for SubChunkBlocks {}
685
686
687/// A sub chunk, 16x16x16 blocks.
688pub struct SubChunk {
689    /// A local shared pointer to the level environment.
690    env: Arc<LevelEnv>,
691    /// Local of global blocks palette.
692    blocks_palette: SubChunkBlocks,
693    /// Cube blocks array.
694    blocks: PackedArray,
695    /// Non-null blocks count. "Null block" is a shorthand for the block state at save ID 0 in the
696    /// global palette, likely to be an 'air' block state for example in vanilla. This number must
697    /// be between 0 (inclusive) and 4096 (exclusive), other values are unsafe.
698    non_null_blocks_count: u16,
699}
700
701// Palette capacity must ensure that most of the chunks will be supported AND that the palette
702// lookup is fast, but because the lookup is O(N) we have to find a balanced capacity.
703const BLOCKS_PALETTE_CAPACITY: usize = 128;
704
705// Minimum size (in bits) of bytes representing block indices to local or global palette.
706const BLOCKS_ARRAY_MIN_BYTE_SIZE: u8 = 4;
707
708
709impl SubChunk {
710
711    /// Construct a new sub chunk with the given level environment. The default block is
712    /// the "null-block" which is the block with save ID 0 in the global blocks palette
713    /// (which is air in vanilla global palette).
714    pub fn new(env: Arc<LevelEnv>) -> Self {
715
716        let null_block = env.blocks.get_state_from(0)
717            .expect("An global blocks palette is not supported by SubChunk.");
718
719        // The palettes are initialized with an initial state and biome (usually air and void, if
720        // vanilla environment), this is required because the packed array has default values of 0,
721        // and they must have a corresponding valid value in palettes, at least at the beginning.
722        SubChunk {
723            env,
724            blocks_palette: SubChunkBlocks::Local {
725                palette: Palette::with_default(null_block, BLOCKS_PALETTE_CAPACITY),
726                null_block_sid: 0
727            },
728            blocks: PackedArray::new(BLOCKS_DATA_SIZE, BLOCKS_ARRAY_MIN_BYTE_SIZE, None),
729            non_null_blocks_count: 0,
730        }
731
732    }
733
734    #[inline]
735    fn get_blocks_byte_size(env: &LevelEnv) -> u8 {
736        PackedArray::calc_min_byte_size(env.blocks.states_count() as u64 - 1)
737            .max(BLOCKS_ARRAY_MIN_BYTE_SIZE)
738    }
739
740    // BLOCKS //
741
742    pub fn get_block(&self, x: u8, y: u8, z: u8) -> &'static BlockState {
743        // SAFETY: The unwrap should be safe because the block index is expected to be right,
744        //         moreover it is checked in debug mode.
745        let sid = self.blocks.get(calc_block_index(x, y, z)).unwrap() as u32;
746        self.get_block_from_sid(sid)
747    }
748
749    /// # Caution
750    /// This method will not update heightmaps of the owner chunk.
751    pub fn set_block(&mut self, x: u8, y: u8, z: u8, state: &'static BlockState) -> ChunkResult<()> {
752        let idx = calc_block_index(x, y, z);
753        match self.ensure_block_sid(state) {
754            Some(sid) => {
755                let old_sid = self.blocks.set(idx, sid as u64) as u32;
756                if let Some(null_block_sid) = self.blocks_palette.get_null_block_sid() {
757                    let was_null = old_sid == null_block_sid;
758                    let is_null = sid == null_block_sid;
759                    self.non_null_blocks_count = (self.non_null_blocks_count as i16 + was_null as i16 - is_null as i16) as u16;
760                }
761                Ok(())
762            },
763            None => Err(ChunkError::IllegalBlock)
764        }
765    }
766
767    /// Internal method to get the block state reference from its save ID. It is internal because
768    /// calling this method with invalid save ID for the sub chunk will panic.
769    #[inline]
770    fn get_block_from_sid(&self, sid: u32) -> &'static BlockState {
771        // SAFETY: The unwraps should be safe in this case because the `set_block` ensure
772        //         that the palette is updated according to the blocks contents. If the user
773        //         directly manipulate the contents of palette or blocks array it MUST ensure
774        //         that is correct, if not this will panic here.
775        match self.blocks_palette {
776            SubChunkBlocks::Local { ref palette, .. } => unsafe {
777                // Here we transmute a `*const BlockState` to `&'static BlockState`, this is safe.
778                std::mem::transmute(palette.get_item(sid as usize).unwrap())
779            },
780            SubChunkBlocks::Global => self.env.blocks.get_state_from(sid).unwrap()
781        }
782    }
783
784    /// Internal method to get the save ID of a given block state within this chunk. If this
785    /// block state is not currently registered, a new save ID will be allocated. If there
786    /// are more than `BLOCKS_PALETTE_CAPACITY` blocks in the local palette, the whole blocks
787    /// packed array will be resized in order to store global save ID instead of local ones.
788    fn ensure_block_sid(&mut self, state: &'static BlockState) -> Option<u32> {
789
790        if let SubChunkBlocks::Local {
791            ref mut palette,
792            ..
793        } = self.blocks_palette {
794
795            let state_ptr = state as *const BlockState;
796
797            // Here we intentionally dont use palette.ensure_index, because we want to check if
798            // the given state is supported by the underlying global palette before actually
799            // inserting it to the local palette.
800            match palette.search_index(state_ptr) {
801                Some(sid) => return Some(sid as u32),
802                None => {
803                    if self.env.blocks.has_state(state) {
804                        match palette.insert_index(state_ptr) {
805                            Some(sid) => {
806                                if sid as u64 > self.blocks.max_value() {
807                                    self.blocks.resize_byte(self.blocks.byte_size() + 1);
808                                }
809                                return Some(sid as u32);
810                            },
811                            None => {
812                                // In this case, the local palette is full, we have to switch to
813                                // the global one. So we don't return anything to skip the match.
814                            }
815                        }
816                    } else {
817                        return None;
818                    }
819                }
820            }
821
822            self.use_global_blocks();
823
824        }
825
826        self.env.blocks.get_sid_from(state)
827
828    }
829
830    fn use_global_blocks(&mut self) {
831        if let SubChunkBlocks::Local {
832            ref palette,
833            ..
834        } = self.blocks_palette {
835            let new_byte_size = Self::get_blocks_byte_size(&self.env);
836            let global_palette = &self.env.blocks;
837            self.blocks.resize_byte_and_replace(new_byte_size, move |_, sid| {
838                let state = palette.get_item(sid as usize).unwrap();
839                global_palette.get_sid_from(unsafe { std::mem::transmute(state) }).unwrap() as u64
840            });
841            self.blocks_palette = SubChunkBlocks::Global;
842        }
843    }
844
845    /// Force fill all the sub chunk with the given block state.
846    pub fn fill_block(&mut self, state: &'static BlockState) -> ChunkResult<()> {
847
848        let null_block_sid = match self.env.blocks.get_sid_from(state) {
849            None => return Err(ChunkError::IllegalBlock),
850            Some(0) => {
851                self.non_null_blocks_count = 0;
852                0
853            }
854            Some(_) => {
855                self.non_null_blocks_count = 4096;
856                u32::MAX
857            }
858        };
859
860        self.blocks_palette = SubChunkBlocks::Local {
861            palette: Palette::with_default(state, BLOCKS_PALETTE_CAPACITY),
862            null_block_sid
863        };
864
865        unsafe {
866            // SAFETY: We don't care of the old content, and clearing all cells will set every
867            // value to 0, which points to the new state at save ID 0 in the palette.
868            self.blocks.resize_raw(BLOCKS_ARRAY_MIN_BYTE_SIZE);
869            self.blocks.clear_cells();
870        }
871
872        Ok(())
873
874    }
875
876    // Raw manipulations //
877
878    /// # Safety:
879    /// You must ensure that the given palette contains only valid states for this
880    /// chunk's level's environment.
881    ///
882    /// The blocks iterator must return only valid indices for the given palette.
883    pub unsafe fn set_blocks_raw<I>(&mut self, palette: Vec<&'static BlockState>, mut blocks: I)
884    where
885        I: Iterator<Item = usize>
886    {
887
888        assert_ne!(palette.len(), 0, "Palette length is zero.");
889
890        if palette.len() <= BLOCKS_PALETTE_CAPACITY {
891
892            let palette = cast_vec_ref_to_ptr(palette);
893            let byte_size = PackedArray::calc_min_byte_size(palette.len() as u64 - 1)
894                .max(BLOCKS_ARRAY_MIN_BYTE_SIZE);
895
896            self.blocks_palette = SubChunkBlocks::Local {
897                null_block_sid: {
898                    let null_block = self.env.blocks.get_state_from(0).unwrap();
899                    palette.iter()
900                        .position(move |&v| v == null_block)
901                        .map(|idx| idx as u32)
902                        .unwrap_or(u32::MAX)
903                },
904                palette: Palette::from_raw(palette, BLOCKS_PALETTE_CAPACITY)
905            };
906
907            // We resize raw because we don't care of the old content, and the new byte size might
908            // be smaller than the old one.
909            self.blocks.resize_raw(byte_size);
910            self.blocks.replace(move |_, _| {
911                blocks.next().unwrap_or(0) as u64
912            });
913
914        } else {
915
916            self.blocks_palette = SubChunkBlocks::Global;
917
918            let byte_size = Self::get_blocks_byte_size(&self.env);
919            let global_blocks = &self.env.blocks;
920
921            self.blocks.resize_raw(byte_size);
922            self.blocks.replace(move |_, _| {
923                let palette_idx = blocks.next().unwrap_or(0);
924                let state = palette[palette_idx];
925                // SAFETY: We can unwrap because this is an safety condition of the method.
926                global_blocks.get_sid_from(state).unwrap() as u64
927            });
928
929        }
930
931        self.refresh_non_null_blocks_count();
932
933    }
934
935    /// Iterate over all blocks in this chunk, ordered by X, Z then Y.
936    pub fn iter_blocks(&self) -> impl Iterator<Item = &'static BlockState> + '_ {
937        SubChunkBlocksIter {
938            inner: self.blocks.iter(),
939            sub_chunk: self,
940            last_block: None
941        }
942    }
943
944    fn refresh_non_null_blocks_count(&mut self) {
945        if let Some(null_block_sid) = self.blocks_palette.get_null_block_sid() {
946            self.non_null_blocks_count = self.blocks.iter()
947                .filter(move |&sid| sid != null_block_sid as u64)
948                .count() as u16;
949        } else {
950            self.non_null_blocks_count = 4096; // The is no null block.
951        }
952    }
953
954    // Meta info //
955
956    #[inline]
957    pub fn non_null_blocks_count(&self) -> u16 {
958        self.non_null_blocks_count
959    }
960
961    #[inline]
962    pub fn null_blocks_count(&self) -> u16 {
963        4096 - self.non_null_blocks_count
964    }
965
966    #[inline]
967    pub fn has_non_null_block(&self) -> bool {
968        self.non_null_blocks_count != 0
969    }
970
971}
972
973
974/// An efficient iterator over all blocks in a sub chunk.
975pub struct SubChunkBlocksIter<'a, I> {
976    inner: I,
977    sub_chunk: &'a SubChunk,
978    last_block: Option<(u64, &'static BlockState)>
979}
980
981impl<'a, I> Iterator for SubChunkBlocksIter<'a, I>
982where
983    I: Iterator<Item = u64>
984{
985
986    type Item = &'static BlockState;
987
988    fn next(&mut self) -> Option<Self::Item> {
989        match self.inner.next() {
990            None => None,
991            Some(sid) => {
992                match self.last_block {
993                    Some((last_sid, state)) if last_sid == sid => Some(state),
994                    ref mut last_block => {
995                        Some(&last_block.insert((sid, self.sub_chunk.get_block_from_sid(sid as u32))).1)
996                    }
997                }
998            }
999        }
1000    }
1001
1002}
1003
1004
1005/// A little structure that stores the height of a level (and so a chunk).
1006#[derive(Debug, Clone, Copy)]
1007pub struct ChunkHeight {
1008    /// Inclusive lower bound.
1009    pub min: i8,
1010    /// Inclusive upper bound.
1011    pub max: i8,
1012}
1013
1014impl ChunkHeight {
1015
1016    pub fn new(min: i8, max: i8) -> Self {
1017        Self { min, max }
1018    }
1019
1020    /// Returns the inclusive lower bound in blocks coordinates.
1021    #[inline]
1022    pub fn get_min_block(self) -> i32 {
1023        (self.min as i32) * 16
1024    }
1025
1026    /// Returns the inclusive upper bound in blocks coordinates.
1027    #[inline]
1028    pub fn get_max_block(self) -> i32 {
1029        (self.max as i32) * 16 + 15
1030    }
1031
1032    /// Return `true` if the given chunk Y coordinate is valid for the specified height.
1033    #[inline]
1034    pub fn contains(self, cy: i8) -> bool {
1035        return self.min <= cy && cy <= self.max;
1036    }
1037
1038    /// Return the number of chunks in this chunk height.
1039    #[inline]
1040    pub fn len(self) -> usize {
1041        (self.max - self.min + 1) as usize
1042    }
1043
1044    /// Iterate over all chunk Y coordinates in this chunk height.
1045    #[inline]
1046    pub fn iter(self) -> impl Iterator<Item = i8> {
1047        self.min..=self.max
1048    }
1049
1050}
1051
1052
1053#[inline]
1054fn calc_block_index(x: u8, y: u8, z: u8) -> usize {
1055    debug_assert!(x < 16 && y < 16 && z < 16, "x: {}, y: {}, z: {}", x, y, z);
1056    x as usize | ((z as usize) << 4) | ((y as usize) << 8)
1057}
1058
1059
1060#[inline]
1061fn calc_biome_index(x: u8, y: usize, z: u8) -> usize {
1062    debug_assert!(x < 4 && z < 4, "x: {}, z: {}", x, z);
1063    x as usize | ((z as usize) << 2) | (y << 4)
1064}
1065
1066
1067#[inline]
1068fn calc_heightmap_index(x: u8, z: u8) -> usize {
1069    debug_assert!(x < 16 && z < 16, "x: {}, z: {}", x, z);
1070    x as usize | ((z as usize) << 4)
1071}
1072
1073
1074#[cfg(test)]
1075mod tests {
1076
1077    use super::*;
1078    use crate::block::GlobalBlocks;
1079    use crate::biome::GlobalBiomes;
1080    use crate::entity::GlobalEntities;
1081    use crate::heightmap::GlobalHeightmaps;
1082
1083    crate::blocks!(TEST_BLOCKS "test" [
1084        AIR "air",
1085        STONE "stone",
1086        DIRT "dirt"
1087    ]);
1088
1089    crate::biomes!(TEST_BIOMES "test" [
1090        VOID "void" 0,
1091    ]);
1092
1093    fn heightmap_test(state: &'static BlockState, _blocks: &GlobalBlocks) -> bool {
1094        state == STONE.get_default_state()
1095    }
1096
1097    crate::heightmaps!(TEST_HEIGHTMAPS [
1098        TEST "TEST" heightmap_test
1099    ]);
1100
1101    fn build_chunk() -> Chunk {
1102        let env = Arc::new(LevelEnv::new(
1103            GlobalBlocks::with_all(&TEST_BLOCKS).unwrap(),
1104            GlobalBiomes::with_all(&TEST_BIOMES).unwrap(),
1105            GlobalEntities::new(),
1106            GlobalHeightmaps::with_all(&TEST_HEIGHTMAPS)
1107        ));
1108        Chunk::new(env, ChunkHeight::new(-1, 2), 0, 0)
1109    }
1110
1111    #[test]
1112    fn valid_height() {
1113        let mut chunk = build_chunk();
1114        assert!(matches!(chunk.ensure_sub_chunk(-2), Err(ChunkError::SubChunkOutOfRange)));
1115        assert!(matches!(chunk.ensure_sub_chunk(-1), Ok(_)));
1116        assert!(matches!(chunk.ensure_sub_chunk(0), Ok(_)));
1117        assert!(matches!(chunk.ensure_sub_chunk(1), Ok(_)));
1118        assert!(matches!(chunk.ensure_sub_chunk(2), Ok(_)));
1119        assert!(matches!(chunk.ensure_sub_chunk(3), Err(ChunkError::SubChunkOutOfRange)));
1120    }
1121
1122    #[test]
1123    fn valid_set_get() {
1124        let mut chunk = build_chunk();
1125        chunk.ensure_sub_chunk(-1).unwrap();
1126        assert_eq!(chunk.get_block(0, -16, 0).unwrap(), AIR.get_default_state());
1127        assert_eq!(chunk.get_block(0, 0, 0).unwrap(), AIR.get_default_state());
1128        assert!(matches!(chunk.get_block(0, -17, 0), Err(ChunkError::SubChunkOutOfRange)));
1129        assert!(matches!(chunk.set_block(0, 0, 0, STONE.get_default_state()), Ok(_)));
1130        assert_eq!(chunk.get_block(0, 0, 0).unwrap(), STONE.get_default_state());
1131    }
1132
1133    #[test]
1134    fn valid_heightmap() {
1135        let mut chunk = build_chunk();
1136        assert!(matches!(chunk.get_heightmap_column(&TEST, 0, 0), Ok(-16)));
1137        chunk.set_block(0, -16, 0, STONE.get_default_state()).unwrap();
1138        assert!(matches!(chunk.get_heightmap_column(&TEST, 0, 0), Ok(-15)));
1139        chunk.set_block(0, 0, 0, STONE.get_default_state()).unwrap();
1140        chunk.set_block(0, -1, 0, STONE.get_default_state()).unwrap();
1141        assert!(matches!(chunk.get_heightmap_column(&TEST, 0, 0), Ok(1)));
1142        chunk.set_block(0, 0, 0, AIR.get_default_state()).unwrap();
1143        assert!(matches!(chunk.get_heightmap_column(&TEST, 0, 0), Ok(0)));
1144        chunk.set_block(0, -1, 0, AIR.get_default_state()).unwrap();
1145        assert!(matches!(chunk.get_heightmap_column(&TEST, 0, 0), Ok(-15)));
1146        chunk.set_block(0, -16, 0, AIR.get_default_state()).unwrap();
1147        assert!(matches!(chunk.get_heightmap_column(&TEST, 0, 0), Ok(-16)));
1148        chunk.set_block(0, 47, 0, STONE.get_default_state()).unwrap();
1149        assert!(matches!(chunk.get_heightmap_column(&TEST, 0, 0), Ok(48)));
1150    }
1151
1152    #[test]
1153    fn valid_non_null_blocks_count() {
1154
1155        let mut chunk = build_chunk();
1156        let sub_chunk = chunk.ensure_sub_chunk(0).unwrap();
1157        assert_eq!(sub_chunk.null_blocks_count(), 4096);
1158        assert_eq!(sub_chunk.non_null_blocks_count(), 0);
1159        sub_chunk.set_block(0, 0, 0, STONE.get_default_state()).unwrap();
1160        assert_eq!(sub_chunk.non_null_blocks_count(), 1);
1161        sub_chunk.set_block(0, 1, 0, STONE.get_default_state()).unwrap();
1162        assert_eq!(sub_chunk.non_null_blocks_count(), 2);
1163        sub_chunk.set_block(0, 1, 0, DIRT.get_default_state()).unwrap();
1164        assert_eq!(sub_chunk.non_null_blocks_count(), 2);
1165        sub_chunk.set_block(0, 0, 0, AIR.get_default_state()).unwrap();
1166        assert_eq!(sub_chunk.non_null_blocks_count(), 1);
1167
1168        sub_chunk.fill_block(AIR.get_default_state()).unwrap();
1169        assert_eq!(sub_chunk.non_null_blocks_count(), 0);
1170        sub_chunk.fill_block(DIRT.get_default_state()).unwrap();
1171        assert_eq!(sub_chunk.null_blocks_count(), 0);
1172
1173        unsafe {
1174
1175            sub_chunk.set_blocks_raw(vec![STONE.get_default_state()], (0..4096).map(|_| 0));
1176            assert_eq!(sub_chunk.null_blocks_count(), 0);
1177
1178            sub_chunk.set_blocks_raw(vec![AIR.get_default_state()], (0..4096).map(|_| 0));
1179            assert_eq!(sub_chunk.non_null_blocks_count(), 0);
1180
1181        }
1182
1183    }
1184
1185}