azalea_world/
chunk_storage.rs

1use std::{
2    collections::{HashMap, hash_map::Entry},
3    fmt,
4    fmt::Debug,
5    io,
6    io::{Cursor, Write},
7    sync::{Arc, Weak},
8};
9
10use azalea_block::{
11    block_state::{BlockState, BlockStateIntegerRepr},
12    fluid_state::FluidState,
13};
14use azalea_buf::{AzaleaRead, AzaleaWrite, BufReadError};
15use azalea_core::position::{
16    BlockPos, ChunkBiomePos, ChunkBlockPos, ChunkPos, ChunkSectionBiomePos, ChunkSectionBlockPos,
17};
18use azalea_registry::Biome;
19use nohash_hasher::IntMap;
20use parking_lot::RwLock;
21use tracing::{debug, trace, warn};
22
23use crate::{
24    heightmap::{Heightmap, HeightmapKind},
25    palette::PalettedContainer,
26};
27
28const SECTION_HEIGHT: u32 = 16;
29
30/// An efficient storage of chunks for a client that has a limited render
31/// distance. This has support for using a shared [`ChunkStorage`].
32pub struct PartialChunkStorage {
33    /// The center of the view, i.e. the chunk the player is currently in.
34    view_center: ChunkPos,
35    pub(crate) chunk_radius: u32,
36    view_range: u32,
37    // chunks is a list of size chunk_radius * chunk_radius
38    chunks: Box<[Option<Arc<RwLock<Chunk>>>]>,
39}
40
41/// A storage for chunks where they're only stored weakly, so if they're not
42/// actively being used somewhere else they'll be forgotten. This is used for
43/// shared worlds.
44///
45/// This is relatively cheap to clone since it's just an `IntMap` with `Weak`
46/// pointers.
47#[derive(Debug, Clone)]
48pub struct ChunkStorage {
49    pub height: u32,
50    pub min_y: i32,
51    pub map: IntMap<ChunkPos, Weak<RwLock<Chunk>>>,
52}
53
54/// A single chunk in a world (16*?*16 blocks). This only contains the blocks
55/// and biomes. You can derive the height of the chunk from the number of
56/// sections, but you need a [`ChunkStorage`] to get the minimum Y
57/// coordinate.
58#[derive(Debug)]
59pub struct Chunk {
60    pub sections: Box<[Section]>,
61    /// Heightmaps are used for identifying the surface blocks in a chunk.
62    /// Usually for clients only `WorldSurface` and `MotionBlocking` are
63    /// present.
64    pub heightmaps: HashMap<HeightmapKind, Heightmap>,
65}
66
67/// A section of a chunk, i.e. a 16*16*16 block area.
68#[derive(Clone, Debug, Default)]
69pub struct Section {
70    pub block_count: u16,
71    pub states: PalettedContainer<BlockState>,
72    pub biomes: PalettedContainer<Biome>,
73}
74
75/// Get the actual stored view distance for the selected view distance. For some
76/// reason Minecraft actually stores an extra 3 chunks.
77pub fn calculate_chunk_storage_range(view_distance: u32) -> u32 {
78    u32::max(view_distance, 2) + 3
79}
80
81impl Default for Chunk {
82    fn default() -> Self {
83        Chunk {
84            sections: vec![Section::default(); (384 / 16) as usize].into(),
85            heightmaps: HashMap::new(),
86        }
87    }
88}
89
90impl PartialChunkStorage {
91    pub fn new(chunk_radius: u32) -> Self {
92        let view_range = chunk_radius * 2 + 1;
93        PartialChunkStorage {
94            view_center: ChunkPos::new(0, 0),
95            chunk_radius,
96            view_range,
97            chunks: vec![None; (view_range * view_range) as usize].into(),
98        }
99    }
100
101    /// Update the chunk to center the view on. This should be called when the
102    /// client receives a `SetChunkCacheCenter` packet.
103    pub fn update_view_center(&mut self, view_center: ChunkPos) {
104        // this code block makes it force unload the chunks that are out of range after
105        // updating the view center. it's usually fine without it but the commented code
106        // is there in case you want to temporarily uncomment to test something
107
108        // ```
109        // for index in 0..self.chunks.len() {
110        //     let chunk_pos = self.chunk_pos_from_index(index);
111        //     if !in_range_for_view_center_and_radius(&chunk_pos, view_center, self.chunk_radius) {
112        //         self.chunks[index] = None;
113        //     }
114        // }
115        // ```
116
117        self.view_center = view_center;
118    }
119
120    /// Get the center of the view. This is usually the chunk that the player is
121    /// in.
122    pub fn view_center(&self) -> ChunkPos {
123        self.view_center
124    }
125
126    pub fn view_range(&self) -> u32 {
127        self.view_range
128    }
129
130    pub fn index_from_chunk_pos(&self, chunk_pos: &ChunkPos) -> usize {
131        let view_range = self.view_range as i32;
132
133        let x = i32::rem_euclid(chunk_pos.x, view_range) * view_range;
134        let z = i32::rem_euclid(chunk_pos.z, view_range);
135        (x + z) as usize
136    }
137
138    pub fn chunk_pos_from_index(&self, index: usize) -> ChunkPos {
139        let view_range = self.view_range as i32;
140
141        // find the base from the view center
142        let base_x = self.view_center.x.div_euclid(view_range) * view_range;
143        let base_z = self.view_center.z.div_euclid(view_range) * view_range;
144
145        // add the offset from the base
146        let offset_x = index as i32 / view_range;
147        let offset_z = index as i32 % view_range;
148
149        ChunkPos::new(base_x + offset_x, base_z + offset_z)
150    }
151
152    pub fn in_range(&self, chunk_pos: &ChunkPos) -> bool {
153        in_range_for_view_center_and_radius(chunk_pos, self.view_center, self.chunk_radius)
154    }
155
156    pub fn set_block_state(
157        &self,
158        pos: &BlockPos,
159        state: BlockState,
160        chunk_storage: &ChunkStorage,
161    ) -> Option<BlockState> {
162        if pos.y < chunk_storage.min_y
163            || pos.y >= (chunk_storage.min_y + chunk_storage.height as i32)
164        {
165            return None;
166        }
167        let chunk_pos = ChunkPos::from(pos);
168        let chunk_lock = chunk_storage.get(&chunk_pos)?;
169        let mut chunk = chunk_lock.write();
170        Some(chunk.get_and_set_block_state(&ChunkBlockPos::from(pos), state, chunk_storage.min_y))
171    }
172
173    pub fn replace_with_packet_data(
174        &mut self,
175        pos: &ChunkPos,
176        data: &mut Cursor<&[u8]>,
177        heightmaps: &[(HeightmapKind, Box<[u64]>)],
178        chunk_storage: &mut ChunkStorage,
179    ) -> Result<(), BufReadError> {
180        debug!("Replacing chunk at {:?}", pos);
181        if !self.in_range(pos) {
182            warn!("Ignoring chunk since it's not in the view range: {pos:?}");
183            return Ok(());
184        }
185
186        let chunk = Chunk::read_with_dimension_height(
187            data,
188            chunk_storage.height,
189            chunk_storage.min_y,
190            heightmaps,
191        )?;
192
193        self.set(pos, Some(chunk), chunk_storage);
194        trace!("Loaded chunk {pos:?}");
195
196        Ok(())
197    }
198
199    /// Get a [`Chunk`] within render distance, or `None` if it's not loaded.
200    /// Use [`ChunkStorage::get`] to get a chunk from the shared storage.
201    pub fn limited_get(&self, pos: &ChunkPos) -> Option<&Arc<RwLock<Chunk>>> {
202        if !self.in_range(pos) {
203            warn!(
204                "Chunk at {:?} is not in the render distance (center: {:?}, {} chunks)",
205                pos, self.view_center, self.chunk_radius,
206            );
207            return None;
208        }
209
210        let index = self.index_from_chunk_pos(pos);
211        self.chunks[index].as_ref()
212    }
213    /// Get a mutable reference to a [`Chunk`] within render distance, or
214    /// `None` if it's not loaded. Use [`ChunkStorage::get`] to get
215    /// a chunk from the shared storage.
216    pub fn limited_get_mut(&mut self, pos: &ChunkPos) -> Option<&mut Option<Arc<RwLock<Chunk>>>> {
217        if !self.in_range(pos) {
218            return None;
219        }
220
221        let index = self.index_from_chunk_pos(pos);
222
223        Some(&mut self.chunks[index])
224    }
225
226    /// Set a chunk in the shared storage and reference it from the limited
227    /// storage. Use [`Self::limited_set`] if you already have an
228    /// `Arc<RwLock<Chunk>>`.
229    ///
230    /// # Panics
231    /// If the chunk is not in the render distance.
232    pub fn set(&mut self, pos: &ChunkPos, chunk: Option<Chunk>, chunk_storage: &mut ChunkStorage) {
233        let new_chunk;
234
235        // add the chunk to the shared storage
236        if let Some(chunk) = chunk {
237            match chunk_storage.map.entry(*pos) {
238                Entry::Occupied(mut e) => {
239                    if let Some(old_chunk) = e.get_mut().upgrade() {
240                        *old_chunk.write() = chunk;
241                        new_chunk = Some(old_chunk);
242                    } else {
243                        let chunk_lock = Arc::new(RwLock::new(chunk));
244                        e.insert(Arc::downgrade(&chunk_lock));
245                        new_chunk = Some(chunk_lock);
246                    }
247                }
248                Entry::Vacant(e) => {
249                    let chunk_lock = Arc::new(RwLock::new(chunk));
250                    e.insert(Arc::downgrade(&chunk_lock));
251                    new_chunk = Some(chunk_lock);
252                }
253            }
254        } else {
255            // don't remove it from the shared storage, since it'll be removed
256            // automatically if this was the last reference
257
258            new_chunk = None;
259        }
260
261        self.limited_set(pos, new_chunk);
262    }
263
264    /// Set a chunk in our limited storage, useful if your chunk is already
265    /// referenced somewhere else and you want to make it also be referenced by
266    /// this storage.
267    ///
268    /// Use [`Self::set`] if you don't already have an `Arc<RwLock<Chunk>>`.
269    ///
270    /// # Panics
271    /// If the chunk is not in the render distance.
272    pub fn limited_set(&mut self, pos: &ChunkPos, chunk: Option<Arc<RwLock<Chunk>>>) {
273        if let Some(chunk_mut) = self.limited_get_mut(pos) {
274            *chunk_mut = chunk;
275        }
276    }
277
278    /// Get an iterator over all the chunks in the storage.
279    pub fn chunks(&self) -> impl Iterator<Item = &Option<Arc<RwLock<Chunk>>>> {
280        self.chunks.iter()
281    }
282}
283impl ChunkStorage {
284    pub fn new(height: u32, min_y: i32) -> Self {
285        ChunkStorage {
286            height,
287            min_y,
288            map: IntMap::default(),
289        }
290    }
291
292    pub fn get(&self, pos: &ChunkPos) -> Option<Arc<RwLock<Chunk>>> {
293        self.map.get(pos).and_then(|chunk| chunk.upgrade())
294    }
295
296    pub fn get_block_state(&self, pos: &BlockPos) -> Option<BlockState> {
297        let chunk_pos = ChunkPos::from(pos);
298        let chunk = self.get(&chunk_pos)?;
299        let chunk = chunk.read();
300        chunk.get_block_state(&ChunkBlockPos::from(pos), self.min_y)
301    }
302
303    pub fn get_fluid_state(&self, pos: &BlockPos) -> Option<FluidState> {
304        let block_state = self.get_block_state(pos)?;
305        Some(FluidState::from(block_state))
306    }
307
308    pub fn get_biome(&self, pos: &BlockPos) -> Option<Biome> {
309        let chunk_pos = ChunkPos::from(pos);
310        let chunk = self.get(&chunk_pos)?;
311        let chunk = chunk.read();
312        chunk.get_biome(&ChunkBiomePos::from(pos), self.min_y)
313    }
314
315    pub fn set_block_state(&self, pos: &BlockPos, state: BlockState) -> Option<BlockState> {
316        if pos.y < self.min_y || pos.y >= (self.min_y + self.height as i32) {
317            return None;
318        }
319        let chunk_pos = ChunkPos::from(pos);
320        let chunk = self.get(&chunk_pos)?;
321        let mut chunk = chunk.write();
322        Some(chunk.get_and_set_block_state(&ChunkBlockPos::from(pos), state, self.min_y))
323    }
324}
325
326pub fn in_range_for_view_center_and_radius(
327    chunk_pos: &ChunkPos,
328    view_center: ChunkPos,
329    chunk_radius: u32,
330) -> bool {
331    (chunk_pos.x - view_center.x).unsigned_abs() <= chunk_radius
332        && (chunk_pos.z - view_center.z).unsigned_abs() <= chunk_radius
333}
334
335impl Chunk {
336    pub fn read_with_dimension_height(
337        buf: &mut Cursor<&[u8]>,
338        dimension_height: u32,
339        min_y: i32,
340        heightmaps_data: &[(HeightmapKind, Box<[u64]>)],
341    ) -> Result<Self, BufReadError> {
342        let section_count = dimension_height / SECTION_HEIGHT;
343        let mut sections = Vec::with_capacity(section_count as usize);
344        for _ in 0..section_count {
345            let section = Section::azalea_read(buf)?;
346            sections.push(section);
347        }
348        let sections = sections.into_boxed_slice();
349
350        let mut heightmaps = HashMap::new();
351        for (kind, data) in heightmaps_data {
352            let data: Box<[u64]> = data.clone();
353            let heightmap = Heightmap::new(*kind, dimension_height, min_y, data);
354            heightmaps.insert(*kind, heightmap);
355        }
356
357        Ok(Chunk {
358            sections,
359            heightmaps,
360        })
361    }
362
363    pub fn get_block_state(&self, pos: &ChunkBlockPos, min_y: i32) -> Option<BlockState> {
364        get_block_state_from_sections(&self.sections, pos, min_y)
365    }
366
367    #[must_use = "Use Chunk::set_block_state instead if you don't need the previous state"]
368    pub fn get_and_set_block_state(
369        &mut self,
370        pos: &ChunkBlockPos,
371        state: BlockState,
372        min_y: i32,
373    ) -> BlockState {
374        let section_index = section_index(pos.y, min_y);
375        let Some(section) = self.sections.get_mut(section_index as usize) else {
376            warn!(
377                "Tried to get and set block state {state:?} at out-of-bounds relative chunk position {pos:?}",
378            );
379            return BlockState::AIR;
380        };
381        let chunk_section_pos = ChunkSectionBlockPos::from(pos);
382        let previous_state = section.get_and_set_block_state(chunk_section_pos, state);
383
384        for heightmap in self.heightmaps.values_mut() {
385            heightmap.update(pos, state, &self.sections);
386        }
387
388        previous_state
389    }
390
391    pub fn set_block_state(&mut self, pos: &ChunkBlockPos, state: BlockState, min_y: i32) {
392        let section_index = section_index(pos.y, min_y);
393        let Some(section) = self.sections.get_mut(section_index as usize) else {
394            warn!(
395                "Tried to set block state {state:?} at out-of-bounds relative chunk position {pos:?}",
396            );
397            return;
398        };
399        let chunk_section_pos = ChunkSectionBlockPos::from(pos);
400        section.set_block_state(chunk_section_pos, state);
401
402        for heightmap in self.heightmaps.values_mut() {
403            heightmap.update(pos, state, &self.sections);
404        }
405    }
406
407    pub fn get_biome(&self, pos: &ChunkBiomePos, min_y: i32) -> Option<Biome> {
408        if pos.y < min_y {
409            // y position is out of bounds
410            return None;
411        }
412        let section_index = section_index(pos.y, min_y);
413        let Some(section) = self.sections.get(section_index as usize) else {
414            warn!("Tried to get biome at out-of-bounds relative chunk position {pos:?}",);
415            return None;
416        };
417        let chunk_section_pos = ChunkSectionBiomePos::from(pos);
418        Some(section.get_biome(chunk_section_pos))
419    }
420}
421
422/// Get the block state at the given position from a list of sections. Returns
423/// `None` if the position is out of bounds.
424#[inline]
425pub fn get_block_state_from_sections(
426    sections: &[Section],
427    pos: &ChunkBlockPos,
428    min_y: i32,
429) -> Option<BlockState> {
430    if pos.y < min_y {
431        // y position is out of bounds
432        return None;
433    }
434    let section_index = section_index(pos.y, min_y) as usize;
435    if section_index >= sections.len() {
436        // y position is out of bounds
437        return None;
438    };
439    let section = &sections[section_index];
440    let chunk_section_pos = ChunkSectionBlockPos::from(pos);
441    Some(section.get_block_state(chunk_section_pos))
442}
443
444impl AzaleaWrite for Chunk {
445    fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
446        for section in &self.sections {
447            section.azalea_write(buf)?;
448        }
449        Ok(())
450    }
451}
452
453impl Debug for PartialChunkStorage {
454    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
455        f.debug_struct("PartialChunkStorage")
456            .field("view_center", &self.view_center)
457            .field("chunk_radius", &self.chunk_radius)
458            .field("view_range", &self.view_range)
459            // .field("chunks", &self.chunks)
460            .field("chunks", &format_args!("{} items", self.chunks.len()))
461            .finish()
462    }
463}
464
465impl AzaleaRead for Section {
466    fn azalea_read(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> {
467        let block_count = u16::azalea_read(buf)?;
468
469        // this is commented out because the vanilla server is wrong
470        // TODO: ^ this comment was written ages ago. needs more investigation.
471        // assert!(
472        //     block_count <= 16 * 16 * 16,
473        //     "A section has more blocks than what should be possible. This is a bug!"
474        // );
475
476        let states = PalettedContainer::<BlockState>::read(buf)?;
477
478        for i in 0..states.storage.size() {
479            if !BlockState::is_valid_state(states.storage.get(i) as BlockStateIntegerRepr) {
480                return Err(BufReadError::Custom(format!(
481                    "Invalid block state {} (index {i}) found in section.",
482                    states.storage.get(i)
483                )));
484            }
485        }
486
487        let biomes = PalettedContainer::<Biome>::read(buf)?;
488        Ok(Section {
489            block_count,
490            states,
491            biomes,
492        })
493    }
494}
495
496impl AzaleaWrite for Section {
497    fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
498        self.block_count.azalea_write(buf)?;
499        self.states.azalea_write(buf)?;
500        self.biomes.azalea_write(buf)?;
501        Ok(())
502    }
503}
504
505impl Section {
506    pub fn get_block_state(&self, pos: ChunkSectionBlockPos) -> BlockState {
507        self.states.get(pos)
508    }
509    pub fn get_and_set_block_state(
510        &mut self,
511        pos: ChunkSectionBlockPos,
512        state: BlockState,
513    ) -> BlockState {
514        self.states.get_and_set(pos, state)
515    }
516    pub fn set_block_state(&mut self, pos: ChunkSectionBlockPos, state: BlockState) {
517        self.states.set(pos, state);
518    }
519
520    pub fn get_biome(&self, pos: ChunkSectionBiomePos) -> Biome {
521        self.biomes.get(pos)
522    }
523    pub fn set_biome(&mut self, pos: ChunkSectionBiomePos, biome: Biome) {
524        self.biomes.set(pos, biome);
525    }
526    pub fn get_and_set_biome(&mut self, pos: ChunkSectionBiomePos, biome: Biome) -> Biome {
527        self.biomes.get_and_set(pos, biome)
528    }
529}
530
531impl Default for PartialChunkStorage {
532    fn default() -> Self {
533        Self::new(8)
534    }
535}
536impl Default for ChunkStorage {
537    fn default() -> Self {
538        Self::new(384, -64)
539    }
540}
541
542/// Get the index of where a section is in a chunk based on its y coordinate
543/// and the minimum y coordinate of the world.
544#[inline]
545pub fn section_index(y: i32, min_y: i32) -> u32 {
546    if y < min_y {
547        #[cfg(debug_assertions)]
548        warn!("y ({y}) must be at least {min_y}");
549        #[cfg(not(debug_assertions))]
550        trace!("y ({y}) must be at least {min_y}")
551    };
552    let min_section_index = min_y >> 4;
553    ((y >> 4) - min_section_index) as u32
554}
555
556#[cfg(test)]
557mod tests {
558    use super::*;
559
560    #[test]
561    fn test_section_index() {
562        assert_eq!(section_index(0, 0), 0);
563        assert_eq!(section_index(128, 0), 8);
564        assert_eq!(section_index(127, 0), 7);
565        assert_eq!(section_index(0, -64), 4);
566        assert_eq!(section_index(-64, -64), 0);
567        assert_eq!(section_index(-49, -64), 0);
568        assert_eq!(section_index(-48, -64), 1);
569        assert_eq!(section_index(128, -64), 12);
570    }
571
572    #[test]
573    fn test_out_of_bounds_y() {
574        let mut chunk_storage = ChunkStorage::default();
575        let mut partial_chunk_storage = PartialChunkStorage::default();
576        partial_chunk_storage.set(
577            &ChunkPos { x: 0, z: 0 },
578            Some(Chunk::default()),
579            &mut chunk_storage,
580        );
581        assert!(
582            chunk_storage
583                .get_block_state(&BlockPos { x: 0, y: 319, z: 0 })
584                .is_some()
585        );
586        assert!(
587            chunk_storage
588                .get_block_state(&BlockPos { x: 0, y: 320, z: 0 })
589                .is_none()
590        );
591        assert!(
592            chunk_storage
593                .get_block_state(&BlockPos { x: 0, y: 338, z: 0 })
594                .is_none()
595        );
596        assert!(
597            chunk_storage
598                .get_block_state(&BlockPos { x: 0, y: -64, z: 0 })
599                .is_some()
600        );
601        assert!(
602            chunk_storage
603                .get_block_state(&BlockPos { x: 0, y: -65, z: 0 })
604                .is_none()
605        );
606    }
607
608    #[test]
609    fn test_chunk_pos_from_index() {
610        let mut partial_chunk_storage = PartialChunkStorage::new(5);
611        partial_chunk_storage.update_view_center(ChunkPos::new(0, -1));
612        assert_eq!(
613            partial_chunk_storage.chunk_pos_from_index(
614                partial_chunk_storage.index_from_chunk_pos(&ChunkPos::new(2, -1))
615            ),
616            ChunkPos::new(2, -1),
617        );
618    }
619}