valence_server/layer/chunk/
unloaded.rs

1use std::cmp::Ordering;
2use std::collections::BTreeMap;
3
4use valence_nbt::Compound;
5use valence_protocol::BlockState;
6use valence_registry::biome::BiomeId;
7
8use super::chunk::{
9    check_biome_oob, check_block_oob, check_section_oob, BiomeContainer, BlockStateContainer,
10    Chunk, MAX_HEIGHT, SECTION_BLOCK_COUNT,
11};
12
13#[derive(Clone, Default, Debug)]
14pub struct UnloadedChunk {
15    pub(super) sections: Vec<Section>,
16    pub(super) block_entities: BTreeMap<u32, Compound>,
17}
18
19#[derive(Clone, Default, Debug)]
20pub(super) struct Section {
21    pub(super) block_states: BlockStateContainer,
22    pub(super) biomes: BiomeContainer,
23}
24
25impl UnloadedChunk {
26    pub fn new() -> Self {
27        Self::default()
28    }
29
30    pub fn with_height(height: u32) -> Self {
31        Self {
32            sections: vec![Section::default(); height as usize / 16],
33            block_entities: BTreeMap::new(),
34        }
35    }
36
37    /// Sets the height of this chunk in meters. The chunk is truncated or
38    /// extended with [`BlockState::AIR`] and [`BiomeId::default()`] from the
39    /// top.
40    ///
41    /// The new height should be a multiple of 16 and no more than
42    /// [`MAX_HEIGHT`]. Otherwise, the height is rounded down to the nearest
43    /// valid height.
44    pub fn set_height(&mut self, height: u32) {
45        let new_count = height.min(MAX_HEIGHT) as usize / 16;
46        let old_count = self.sections.len();
47
48        match new_count.cmp(&old_count) {
49            Ordering::Less => {
50                self.sections.truncate(new_count);
51                self.sections.shrink_to_fit();
52
53                let cutoff = SECTION_BLOCK_COUNT as u32 * new_count as u32;
54                self.block_entities.retain(|idx, _| *idx < cutoff);
55            }
56            Ordering::Equal => {}
57            Ordering::Greater => {
58                let diff = new_count - old_count;
59                self.sections.reserve_exact(diff);
60                self.sections.extend((0..diff).map(|_| Section::default()));
61            }
62        }
63    }
64}
65
66impl Chunk for UnloadedChunk {
67    fn height(&self) -> u32 {
68        self.sections.len() as u32 * 16
69    }
70
71    fn block_state(&self, x: u32, y: u32, z: u32) -> BlockState {
72        check_block_oob(self, x, y, z);
73
74        let idx = x + z * 16 + y % 16 * 16 * 16;
75        self.sections[y as usize / 16]
76            .block_states
77            .get(idx as usize)
78    }
79
80    fn set_block_state(&mut self, x: u32, y: u32, z: u32, block: BlockState) -> BlockState {
81        check_block_oob(self, x, y, z);
82
83        let idx = x + z * 16 + y % 16 * 16 * 16;
84        self.sections[y as usize / 16]
85            .block_states
86            .set(idx as usize, block)
87    }
88
89    fn fill_block_state_section(&mut self, sect_y: u32, block: BlockState) {
90        check_section_oob(self, sect_y);
91
92        self.sections[sect_y as usize].block_states.fill(block);
93    }
94
95    fn block_entity(&self, x: u32, y: u32, z: u32) -> Option<&Compound> {
96        check_block_oob(self, x, y, z);
97
98        let idx = x + z * 16 + y * 16 * 16;
99        self.block_entities.get(&idx)
100    }
101
102    fn block_entity_mut(&mut self, x: u32, y: u32, z: u32) -> Option<&mut Compound> {
103        check_block_oob(self, x, y, z);
104
105        let idx = x + z * 16 + y * 16 * 16;
106        self.block_entities.get_mut(&idx)
107    }
108
109    fn set_block_entity(
110        &mut self,
111        x: u32,
112        y: u32,
113        z: u32,
114        block_entity: Option<Compound>,
115    ) -> Option<Compound> {
116        check_block_oob(self, x, y, z);
117
118        let idx = x + z * 16 + y * 16 * 16;
119
120        match block_entity {
121            Some(be) => self.block_entities.insert(idx, be),
122            None => self.block_entities.remove(&idx),
123        }
124    }
125
126    fn clear_block_entities(&mut self) {
127        self.block_entities.clear();
128    }
129
130    fn biome(&self, x: u32, y: u32, z: u32) -> BiomeId {
131        check_biome_oob(self, x, y, z);
132
133        let idx = x + z * 4 + y % 4 * 4 * 4;
134        self.sections[y as usize / 4].biomes.get(idx as usize)
135    }
136
137    fn set_biome(&mut self, x: u32, y: u32, z: u32, biome: BiomeId) -> BiomeId {
138        check_biome_oob(self, x, y, z);
139
140        let idx = x + z * 4 + y % 4 * 4 * 4;
141        self.sections[y as usize / 4]
142            .biomes
143            .set(idx as usize, biome)
144    }
145
146    fn fill_biome_section(&mut self, sect_y: u32, biome: BiomeId) {
147        check_section_oob(self, sect_y);
148
149        self.sections[sect_y as usize].biomes.fill(biome);
150    }
151
152    fn shrink_to_fit(&mut self) {
153        for sect in &mut self.sections {
154            sect.block_states.shrink_to_fit();
155            sect.biomes.shrink_to_fit();
156        }
157    }
158}
159
160#[cfg(test)]
161mod tests {
162    use super::*;
163
164    #[test]
165    fn unloaded_chunk_resize_removes_block_entities() {
166        let mut chunk = UnloadedChunk::with_height(32);
167
168        assert_eq!(chunk.height(), 32);
169
170        // First block entity is in section 0.
171        chunk.set_block_entity(0, 5, 0, Some(Compound::new()));
172
173        // Second block entity is in section 1.
174        chunk.set_block_entity(0, 16, 0, Some(Compound::new()));
175
176        // Remove section 0.
177        chunk.set_height(16);
178        assert_eq!(chunk.height(), 16);
179
180        assert_eq!(chunk.block_entity(0, 5, 0), Some(&Compound::new()));
181        assert_eq!(chunk.set_block_entity(0, 5, 0, None), Some(Compound::new()));
182        assert!(chunk.block_entities.is_empty());
183    }
184}