azalea_world/chunk/
partial.rs1use std::{
2 fmt::{self, Debug},
3 io::Cursor,
4 sync::Arc,
5};
6
7use azalea_block::BlockState;
8use azalea_buf::BufReadError;
9use azalea_core::{
10 heightmap_kind::HeightmapKind,
11 position::{BlockPos, ChunkBlockPos, ChunkPos},
12};
13use parking_lot::RwLock;
14use tracing::{debug, trace, warn};
15
16use crate::{Chunk, chunk::storage::ChunkStorage};
17
18pub struct PartialChunkStorage {
23 view_center: ChunkPos,
25 pub(crate) chunk_radius: u32,
26 view_range: u32,
27 chunks: Box<[Option<Arc<RwLock<Chunk>>>]>,
29}
30
31impl PartialChunkStorage {
32 pub fn new(chunk_radius: u32) -> Self {
33 let view_range = chunk_radius * 2 + 1;
34 PartialChunkStorage {
35 view_center: ChunkPos::new(0, 0),
36 chunk_radius,
37 view_range,
38 chunks: vec![None; (view_range * view_range) as usize].into(),
39 }
40 }
41
42 pub fn update_view_center(&mut self, view_center: ChunkPos) {
47 self.view_center = view_center;
61 }
62
63 pub fn view_center(&self) -> ChunkPos {
66 self.view_center
67 }
68
69 pub fn view_range(&self) -> u32 {
70 self.view_range
71 }
72
73 pub fn index_from_chunk_pos(&self, chunk_pos: &ChunkPos) -> usize {
74 let view_range = self.view_range as i32;
75
76 let x = i32::rem_euclid(chunk_pos.x, view_range) * view_range;
77 let z = i32::rem_euclid(chunk_pos.z, view_range);
78 (x + z) as usize
79 }
80
81 pub fn chunk_pos_from_index(&self, index: usize) -> ChunkPos {
82 let view_range = self.view_range as i32;
83
84 let base_x = self.view_center.x.div_euclid(view_range) * view_range;
86 let base_z = self.view_center.z.div_euclid(view_range) * view_range;
87
88 let offset_x = index as i32 / view_range;
90 let offset_z = index as i32 % view_range;
91
92 ChunkPos::new(base_x + offset_x, base_z + offset_z)
93 }
94
95 pub fn in_range(&self, chunk_pos: &ChunkPos) -> bool {
96 in_range_for_view_center_and_radius(chunk_pos, self.view_center, self.chunk_radius)
97 }
98
99 pub fn set_block_state(
100 &self,
101 pos: BlockPos,
102 state: BlockState,
103 chunk_storage: &ChunkStorage,
104 ) -> Option<BlockState> {
105 if pos.y < chunk_storage.min_y()
106 || pos.y >= (chunk_storage.min_y() + chunk_storage.height() as i32)
107 {
108 return None;
109 }
110 let chunk_pos = ChunkPos::from(pos);
111 let chunk_lock = chunk_storage.get(&chunk_pos)?;
112 let mut chunk = chunk_lock.write();
113 Some(chunk.get_and_set_block_state(&ChunkBlockPos::from(pos), state, chunk_storage.min_y()))
114 }
115
116 pub fn replace_with_packet_data(
117 &mut self,
118 pos: &ChunkPos,
119 data: &mut Cursor<&[u8]>,
120 heightmaps: &[(HeightmapKind, Box<[u64]>)],
121 chunk_storage: &mut ChunkStorage,
122 ) -> Result<(), BufReadError> {
123 debug!("Replacing chunk at {:?}", pos);
124 if !self.in_range(pos) {
125 warn!("Ignoring chunk since it's not in the view range: {pos:?}");
126 return Ok(());
127 }
128
129 let chunk = Chunk::read_with_dimension_height(
130 data,
131 chunk_storage.height(),
132 chunk_storage.min_y(),
133 heightmaps,
134 )?;
135
136 self.set(pos, Some(chunk), chunk_storage);
137 trace!("Loaded chunk {pos:?}");
138
139 Ok(())
140 }
141
142 pub fn limited_get(&self, pos: &ChunkPos) -> Option<&Arc<RwLock<Chunk>>> {
145 if !self.in_range(pos) {
146 warn!(
147 "Chunk at {:?} is not in the render distance (center: {:?}, {} chunks)",
148 pos, self.view_center, self.chunk_radius,
149 );
150 return None;
151 }
152
153 let index = self.index_from_chunk_pos(pos);
154 self.chunks[index].as_ref()
155 }
156 pub fn limited_get_mut(&mut self, pos: &ChunkPos) -> Option<&mut Option<Arc<RwLock<Chunk>>>> {
161 if !self.in_range(pos) {
162 return None;
163 }
164
165 let index = self.index_from_chunk_pos(pos);
166
167 Some(&mut self.chunks[index])
168 }
169
170 pub fn set(&mut self, pos: &ChunkPos, chunk: Option<Chunk>, chunk_storage: &mut ChunkStorage) {
178 let new_chunk = chunk.map(|c| chunk_storage.upsert(*pos, c));
179 self.limited_set(pos, new_chunk);
180 }
181
182 pub fn limited_set(&mut self, pos: &ChunkPos, chunk: Option<Arc<RwLock<Chunk>>>) {
191 if let Some(chunk_mut) = self.limited_get_mut(pos) {
192 *chunk_mut = chunk;
193 }
194 }
195
196 pub fn chunks(&self) -> impl Iterator<Item = &Option<Arc<RwLock<Chunk>>>> {
198 self.chunks.iter()
199 }
200}
201
202impl Debug for PartialChunkStorage {
203 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
204 f.debug_struct("PartialChunkStorage")
205 .field("view_center", &self.view_center)
206 .field("chunk_radius", &self.chunk_radius)
207 .field("view_range", &self.view_range)
208 .field("chunks", &format_args!("{} items", self.chunks.len()))
210 .finish()
211 }
212}
213
214impl Default for PartialChunkStorage {
215 fn default() -> Self {
216 Self::new(8)
217 }
218}
219
220pub fn in_range_for_view_center_and_radius(
221 chunk_pos: &ChunkPos,
222 view_center: ChunkPos,
223 chunk_radius: u32,
224) -> bool {
225 (chunk_pos.x - view_center.x).unsigned_abs() <= chunk_radius
226 && (chunk_pos.z - view_center.z).unsigned_abs() <= chunk_radius
227}
228
229#[cfg(test)]
230mod tests {
231 use azalea_core::position::ChunkPos;
232
233 use crate::chunk::partial::PartialChunkStorage;
234
235 #[test]
236 fn test_chunk_pos_from_index() {
237 let mut partial_chunk_storage = PartialChunkStorage::new(5);
238 partial_chunk_storage.update_view_center(ChunkPos::new(0, -1));
239 assert_eq!(
240 partial_chunk_storage.chunk_pos_from_index(
241 partial_chunk_storage.index_from_chunk_pos(&ChunkPos::new(2, -1))
242 ),
243 ChunkPos::new(2, -1),
244 );
245 }
246}