Skip to main content

azalea_world/chunk/
storage.rs

1use std::{
2    any::Any,
3    collections::hash_map::Entry,
4    fmt::{self, Debug},
5    ops::{Deref, DerefMut},
6    sync::{Arc, Weak},
7};
8
9use azalea_block::{BlockState, fluid_state::FluidState};
10use azalea_core::position::{BlockPos, ChunkBiomePos, ChunkBlockPos, ChunkPos};
11use azalea_registry::data::Biome;
12use nohash_hasher::IntMap;
13use parking_lot::RwLock;
14
15use crate::Chunk;
16
17/// An abstract chunk storage backed by a [`ChunkStorageTrait`] implementation.
18///
19/// By default, this wraps a [`WeakChunkStorage`].
20pub struct ChunkStorage(pub Box<dyn ChunkStorageTrait>);
21
22pub trait ChunkStorageTrait: Send + Sync + Any {
23    /// Return the lowest y coordinate in the world, usually `-64`.
24    fn min_y(&self) -> i32;
25    /// Return the height of the world in blocks, usually `384`.
26    fn height(&self) -> u32;
27    /// Return a reference to the chunk from the storage.
28    #[must_use]
29    fn get(&self, pos: &ChunkPos) -> Option<Arc<RwLock<Chunk>>>;
30    /// Insert the chunk into the storage and return a reference to it.
31    ///
32    /// Since the storage may be a [`WeakChunkStorage`], you must immediately
33    /// put the returned `Arc<RwLock<Chunk>>` somewhere (probably a
34    /// [`PartialChunkStorage`](crate::PartialChunkStorage)).
35    #[must_use]
36    fn upsert(&mut self, pos: ChunkPos, chunk: Chunk) -> Arc<RwLock<Chunk>>;
37    fn chunks(&self) -> Box<[&ChunkPos]>;
38    fn clone_box(&self) -> Box<dyn ChunkStorageTrait>;
39
40    // these impls are here instead of in the `impl ChunkStorage` so rust is able to
41    // inline more and thus optimize them a lot better.
42
43    /// Returns the [`BlockState`] at the given position.
44    ///
45    /// If the block is outside of the world, then `None` is returned.
46    fn get_block_state(&self, pos: BlockPos) -> Option<BlockState> {
47        let chunk = self.get(&ChunkPos::from(pos))?;
48        let chunk = chunk.read();
49        chunk.get_block_state(&ChunkBlockPos::from(pos), self.min_y())
50    }
51    /// Set a [`BlockState`] at the given position.
52    ///
53    /// Returns the block that was previously there, or `None` if the position
54    /// is outside of the world.
55    fn set_block_state(&self, pos: BlockPos, state: BlockState) -> Option<BlockState> {
56        if pos.y < self.min_y() || pos.y >= (self.min_y() + self.height() as i32) {
57            return None;
58        }
59        let chunk = self.get(&ChunkPos::from(pos))?;
60        let mut chunk = chunk.write();
61        Some(chunk.get_and_set_block_state(&ChunkBlockPos::from(pos), state, self.min_y()))
62    }
63    fn get_fluid_state(&self, pos: BlockPos) -> Option<FluidState> {
64        let block_state = self.get_block_state(pos)?;
65        Some(FluidState::from(block_state))
66    }
67    fn get_biome(&self, pos: BlockPos) -> Option<Biome> {
68        let chunk = self.get(&ChunkPos::from(pos))?;
69        let chunk = chunk.read();
70        chunk.get_biome(ChunkBiomePos::from(pos), self.min_y())
71    }
72}
73impl ChunkStorage {
74    /// Create a storage backed by a [`WeakChunkStorage`] with the given world
75    /// dimensions.
76    pub fn new(height: u32, min_y: i32) -> Self {
77        Self(Box::new(WeakChunkStorage::new(height, min_y)))
78    }
79
80    /// Create a storage backed by a custom [`ChunkStorageTrait`]
81    /// implementation.
82    pub fn new_with(inner: Box<dyn ChunkStorageTrait>) -> Self {
83        Self(inner)
84    }
85}
86
87impl Deref for ChunkStorage {
88    type Target = dyn ChunkStorageTrait;
89
90    fn deref(&self) -> &Self::Target {
91        &*self.0
92    }
93}
94impl DerefMut for ChunkStorage {
95    fn deref_mut(&mut self) -> &mut Self::Target {
96        &mut *self.0
97    }
98}
99impl Clone for ChunkStorage {
100    fn clone(&self) -> Self {
101        Self(self.0.clone_box())
102    }
103}
104
105/// A storage for chunks where they're only stored weakly, so if they're not
106/// actively being used somewhere else they'll be forgotten.
107///
108/// This is used for shared worlds.
109///
110/// This is relatively cheap to clone since it's just an `IntMap` with `Weak`
111/// pointers.
112#[derive(Clone, Debug)]
113pub struct WeakChunkStorage {
114    /// The height of the world.
115    ///
116    /// To get the maximum y position (exclusive), you have to combine this with
117    /// [`Self::min_y`].
118    pub height: u32,
119    /// The lowest y position in the world that can still have blocks placed on
120    /// it.
121    ///
122    /// This exists because in modern Minecraft versions, worlds can extend
123    /// below y=0.
124    pub min_y: i32,
125    pub map: IntMap<ChunkPos, Weak<RwLock<Chunk>>>,
126}
127
128impl WeakChunkStorage {
129    pub fn new(height: u32, min_y: i32) -> Self {
130        WeakChunkStorage {
131            height,
132            min_y,
133            map: IntMap::default(),
134        }
135    }
136}
137impl ChunkStorageTrait for WeakChunkStorage {
138    fn min_y(&self) -> i32 {
139        self.min_y
140    }
141    fn height(&self) -> u32 {
142        self.height
143    }
144    fn get(&self, pos: &ChunkPos) -> Option<Arc<RwLock<Chunk>>> {
145        self.map.get(pos).and_then(|chunk| chunk.upgrade())
146    }
147
148    fn upsert(&mut self, pos: ChunkPos, chunk: Chunk) -> Arc<RwLock<Chunk>> {
149        match self.map.entry(pos) {
150            Entry::Occupied(mut e) => {
151                if let Some(existing) = e.get_mut().upgrade() {
152                    *existing.write() = chunk;
153                    existing
154                } else {
155                    let arc = Arc::new(RwLock::new(chunk));
156                    e.insert(Arc::downgrade(&arc));
157                    arc
158                }
159            }
160            Entry::Vacant(e) => {
161                let arc = Arc::new(RwLock::new(chunk));
162                e.insert(Arc::downgrade(&arc));
163                arc
164            }
165        }
166    }
167
168    fn chunks(&self) -> Box<[&ChunkPos]> {
169        self.map.keys().collect::<Vec<_>>().into_boxed_slice()
170    }
171
172    fn clone_box(&self) -> Box<dyn ChunkStorageTrait> {
173        Box::new(self.clone())
174    }
175}
176
177impl Default for WeakChunkStorage {
178    fn default() -> Self {
179        Self::new(384, -64)
180    }
181}
182impl Default for ChunkStorage {
183    fn default() -> Self {
184        Self::new(384, -64)
185    }
186}
187
188impl Debug for ChunkStorage {
189    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
190        f.debug_struct("ChunkStorage")
191            .field("min_y", &self.0.min_y())
192            .field("height", &self.0.height())
193            .field("chunk_count", &self.0.chunks().len())
194            .finish()
195    }
196}
197
198#[cfg(test)]
199mod tests {
200    use azalea_core::position::{BlockPos, ChunkPos};
201
202    use crate::{
203        Chunk,
204        chunk::{partial::PartialChunkStorage, storage::ChunkStorage},
205    };
206
207    #[test]
208    fn test_out_of_bounds_y() {
209        let mut chunk_storage = ChunkStorage::default();
210        let mut partial_chunk_storage = PartialChunkStorage::default();
211        partial_chunk_storage.set(
212            &ChunkPos { x: 0, z: 0 },
213            Some(Chunk::default()),
214            &mut chunk_storage,
215        );
216        assert!(
217            chunk_storage
218                .get_block_state(BlockPos { x: 0, y: 319, z: 0 })
219                .is_some()
220        );
221        assert!(
222            chunk_storage
223                .get_block_state(BlockPos { x: 0, y: 320, z: 0 })
224                .is_none()
225        );
226        assert!(
227            chunk_storage
228                .get_block_state(BlockPos { x: 0, y: 338, z: 0 })
229                .is_none()
230        );
231        assert!(
232            chunk_storage
233                .get_block_state(BlockPos { x: 0, y: -64, z: 0 })
234                .is_some()
235        );
236        assert!(
237            chunk_storage
238                .get_block_state(BlockPos { x: 0, y: -65, z: 0 })
239                .is_none()
240        );
241    }
242}