1use std::collections::hash_map::Entry;
2use std::{
3 collections::HashMap,
4 fmt::Debug,
5 io::{Cursor, Write},
6 sync::{Arc, Weak},
7};
8
9use azalea_block::block_state::{BlockState, BlockStateIntegerRepr};
10use azalea_block::fluid_state::FluidState;
11use azalea_buf::{AzaleaRead, AzaleaWrite, BufReadError};
12use azalea_core::position::{BlockPos, ChunkBlockPos, ChunkPos, ChunkSectionBlockPos};
13use nohash_hasher::IntMap;
14use parking_lot::RwLock;
15use tracing::{debug, trace, warn};
16
17use crate::heightmap::Heightmap;
18use crate::heightmap::HeightmapKind;
19use crate::palette::PalettedContainer;
20use crate::palette::PalettedContainerKind;
21
22const SECTION_HEIGHT: u32 = 16;
23
24pub struct PartialChunkStorage {
27 view_center: ChunkPos,
29 chunk_radius: u32,
30 view_range: u32,
31 chunks: Box<[Option<Arc<RwLock<Chunk>>>]>,
33}
34
35#[derive(Debug, Clone)]
39pub struct ChunkStorage {
40 pub height: u32,
41 pub min_y: i32,
42 pub map: IntMap<ChunkPos, Weak<RwLock<Chunk>>>,
43}
44
45#[derive(Debug)]
50pub struct Chunk {
51 pub sections: Box<[Section]>,
52 pub heightmaps: HashMap<HeightmapKind, Heightmap>,
56}
57
58#[derive(Clone, Debug)]
60pub struct Section {
61 pub block_count: u16,
62 pub states: PalettedContainer,
63 pub biomes: PalettedContainer,
64}
65
66pub fn calculate_chunk_storage_range(view_distance: u32) -> u32 {
69 u32::max(view_distance, 2) + 3
70}
71
72impl Default for Section {
73 fn default() -> Self {
74 Section {
75 block_count: 0,
76 states: PalettedContainer::new(PalettedContainerKind::BlockStates),
77 biomes: PalettedContainer::new(PalettedContainerKind::Biomes),
78 }
79 }
80}
81
82impl Default for Chunk {
83 fn default() -> Self {
84 Chunk {
85 sections: vec![Section::default(); (384 / 16) as usize].into(),
86 heightmaps: HashMap::new(),
87 }
88 }
89}
90
91impl PartialChunkStorage {
92 pub fn new(chunk_radius: u32) -> Self {
93 let view_range = chunk_radius * 2 + 1;
94 PartialChunkStorage {
95 view_center: ChunkPos::new(0, 0),
96 chunk_radius,
97 view_range,
98 chunks: vec![None; (view_range * view_range) as usize].into(),
99 }
100 }
101
102 pub fn update_view_center(&mut self, view_center: ChunkPos) {
105 self.view_center = view_center;
119 }
120
121 pub fn view_center(&self) -> ChunkPos {
124 self.view_center
125 }
126
127 pub fn view_range(&self) -> u32 {
128 self.view_range
129 }
130
131 pub fn index_from_chunk_pos(&self, chunk_pos: &ChunkPos) -> usize {
132 let view_range = self.view_range as i32;
133
134 let x = i32::rem_euclid(chunk_pos.x, view_range) * view_range;
135 let z = i32::rem_euclid(chunk_pos.z, view_range);
136 (x + z) as usize
137 }
138
139 pub fn chunk_pos_from_index(&self, index: usize) -> ChunkPos {
140 let view_range = self.view_range as i32;
141
142 let base_x = self.view_center.x.div_euclid(view_range) * view_range;
144 let base_z = self.view_center.z.div_euclid(view_range) * view_range;
145
146 let offset_x = index as i32 / view_range;
148 let offset_z = index as i32 % view_range;
149
150 ChunkPos::new(base_x + offset_x, base_z + offset_z)
151 }
152
153 pub fn in_range(&self, chunk_pos: &ChunkPos) -> bool {
154 in_range_for_view_center_and_radius(chunk_pos, self.view_center, self.chunk_radius)
155 }
156
157 pub fn set_block_state(
158 &self,
159 pos: &BlockPos,
160 state: BlockState,
161 chunk_storage: &ChunkStorage,
162 ) -> Option<BlockState> {
163 if pos.y < chunk_storage.min_y
164 || pos.y >= (chunk_storage.min_y + chunk_storage.height as i32)
165 {
166 return None;
167 }
168 let chunk_pos = ChunkPos::from(pos);
169 let chunk_lock = chunk_storage.get(&chunk_pos)?;
170 let mut chunk = chunk_lock.write();
171 Some(chunk.get_and_set(&ChunkBlockPos::from(pos), state, chunk_storage.min_y))
172 }
173
174 pub fn replace_with_packet_data(
175 &mut self,
176 pos: &ChunkPos,
177 data: &mut Cursor<&[u8]>,
178 heightmaps: &[(HeightmapKind, Box<[u64]>)],
179 chunk_storage: &mut ChunkStorage,
180 ) -> Result<(), BufReadError> {
181 debug!("Replacing chunk at {:?}", pos);
182 if !self.in_range(pos) {
183 warn!("Ignoring chunk since it's not in the view range: {pos:?}");
184 return Ok(());
185 }
186
187 let chunk = Chunk::read_with_dimension_height(
188 data,
189 chunk_storage.height,
190 chunk_storage.min_y,
191 heightmaps,
192 )?;
193
194 self.set(pos, Some(chunk), chunk_storage);
195 trace!("Loaded chunk {pos:?}");
196
197 Ok(())
198 }
199
200 pub fn limited_get(&self, pos: &ChunkPos) -> Option<&Arc<RwLock<Chunk>>> {
203 if !self.in_range(pos) {
204 warn!(
205 "Chunk at {:?} is not in the render distance (center: {:?}, {} chunks)",
206 pos, self.view_center, self.chunk_radius,
207 );
208 return None;
209 }
210
211 let index = self.index_from_chunk_pos(pos);
212 self.chunks[index].as_ref()
213 }
214 pub fn limited_get_mut(&mut self, pos: &ChunkPos) -> Option<&mut Option<Arc<RwLock<Chunk>>>> {
218 if !self.in_range(pos) {
219 return None;
220 }
221
222 let index = self.index_from_chunk_pos(pos);
223
224 Some(&mut self.chunks[index])
225 }
226
227 pub fn set(&mut self, pos: &ChunkPos, chunk: Option<Chunk>, chunk_storage: &mut ChunkStorage) {
234 let new_chunk;
235
236 if let Some(chunk) = chunk {
238 match chunk_storage.map.entry(*pos) {
239 Entry::Occupied(mut e) => {
240 if let Some(old_chunk) = e.get_mut().upgrade() {
241 *old_chunk.write() = chunk;
242 new_chunk = Some(old_chunk);
243 } else {
244 let chunk_lock = Arc::new(RwLock::new(chunk));
245 e.insert(Arc::downgrade(&chunk_lock));
246 new_chunk = Some(chunk_lock);
247 }
248 }
249 Entry::Vacant(e) => {
250 let chunk_lock = Arc::new(RwLock::new(chunk));
251 e.insert(Arc::downgrade(&chunk_lock));
252 new_chunk = Some(chunk_lock);
253 }
254 }
255 } else {
256 new_chunk = None;
260 }
261
262 self.limited_set(pos, new_chunk);
263 }
264
265 pub fn limited_set(&mut self, pos: &ChunkPos, chunk: Option<Arc<RwLock<Chunk>>>) {
274 if let Some(chunk_mut) = self.limited_get_mut(pos) {
275 *chunk_mut = chunk;
276 }
277 }
278
279 pub fn chunks(&self) -> impl Iterator<Item = &Option<Arc<RwLock<Chunk>>>> {
281 self.chunks.iter()
282 }
283}
284impl ChunkStorage {
285 pub fn new(height: u32, min_y: i32) -> Self {
286 ChunkStorage {
287 height,
288 min_y,
289 map: IntMap::default(),
290 }
291 }
292
293 pub fn get(&self, pos: &ChunkPos) -> Option<Arc<RwLock<Chunk>>> {
294 self.map.get(pos).and_then(|chunk| chunk.upgrade())
295 }
296
297 pub fn get_block_state(&self, pos: &BlockPos) -> Option<BlockState> {
298 let chunk_pos = ChunkPos::from(pos);
299 let chunk = self.get(&chunk_pos)?;
300 let chunk = chunk.read();
301 chunk.get(&ChunkBlockPos::from(pos), self.min_y)
302 }
303
304 pub fn get_fluid_state(&self, pos: &BlockPos) -> Option<FluidState> {
305 let block_state = self.get_block_state(pos)?;
306 Some(FluidState::from(block_state))
307 }
308
309 pub fn set_block_state(&self, pos: &BlockPos, state: BlockState) -> Option<BlockState> {
310 if pos.y < self.min_y || pos.y >= (self.min_y + self.height as i32) {
311 return None;
312 }
313 let chunk_pos = ChunkPos::from(pos);
314 let chunk = self.get(&chunk_pos)?;
315 let mut chunk = chunk.write();
316 Some(chunk.get_and_set(&ChunkBlockPos::from(pos), state, self.min_y))
317 }
318}
319
320pub fn in_range_for_view_center_and_radius(
321 chunk_pos: &ChunkPos,
322 view_center: ChunkPos,
323 chunk_radius: u32,
324) -> bool {
325 (chunk_pos.x - view_center.x).unsigned_abs() <= chunk_radius
326 && (chunk_pos.z - view_center.z).unsigned_abs() <= chunk_radius
327}
328
329impl Chunk {
330 pub fn read_with_dimension_height(
331 buf: &mut Cursor<&[u8]>,
332 dimension_height: u32,
333 min_y: i32,
334 heightmaps_data: &[(HeightmapKind, Box<[u64]>)],
335 ) -> Result<Self, BufReadError> {
336 let section_count = dimension_height / SECTION_HEIGHT;
337 let mut sections = Vec::with_capacity(section_count as usize);
338 for _ in 0..section_count {
339 let section = Section::azalea_read(buf)?;
340 sections.push(section);
341 }
342 let sections = sections.into_boxed_slice();
343
344 let mut heightmaps = HashMap::new();
345 for (kind, data) in heightmaps_data {
346 let data: Box<[u64]> = data.iter().copied().collect();
347 let heightmap = Heightmap::new(*kind, dimension_height, min_y, data);
348 heightmaps.insert(*kind, heightmap);
349 }
350
351 Ok(Chunk {
352 sections,
353 heightmaps,
354 })
355 }
356
357 pub fn get(&self, pos: &ChunkBlockPos, min_y: i32) -> Option<BlockState> {
358 get_block_state_from_sections(&self.sections, pos, min_y)
359 }
360
361 #[must_use = "Use Chunk::set instead if you don't need the previous state"]
362 pub fn get_and_set(
363 &mut self,
364 pos: &ChunkBlockPos,
365 state: BlockState,
366 min_y: i32,
367 ) -> BlockState {
368 let section_index = section_index(pos.y, min_y);
369 let section = &mut self.sections[section_index as usize];
371 let chunk_section_pos = ChunkSectionBlockPos::from(pos);
372 let previous_state = section.get_and_set(chunk_section_pos, state);
373
374 for heightmap in self.heightmaps.values_mut() {
375 heightmap.update(pos, state, &self.sections);
376 }
377
378 previous_state
379 }
380
381 pub fn set(&mut self, pos: &ChunkBlockPos, state: BlockState, min_y: i32) {
382 let section_index = section_index(pos.y, min_y);
383 let section = &mut self.sections[section_index as usize];
385 let chunk_section_pos = ChunkSectionBlockPos::from(pos);
386 section.set(chunk_section_pos, state);
387
388 for heightmap in self.heightmaps.values_mut() {
389 heightmap.update(pos, state, &self.sections);
390 }
391 }
392}
393
394#[inline]
397pub fn get_block_state_from_sections(
398 sections: &[Section],
399 pos: &ChunkBlockPos,
400 min_y: i32,
401) -> Option<BlockState> {
402 if pos.y < min_y {
403 return None;
405 }
406 let section_index = section_index(pos.y, min_y) as usize;
407 if section_index >= sections.len() {
408 return None;
410 };
411 let section = §ions[section_index];
412 let chunk_section_pos = ChunkSectionBlockPos::from(pos);
413 Some(section.get(chunk_section_pos))
414}
415
416impl AzaleaWrite for Chunk {
417 fn azalea_write(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
418 for section in &self.sections {
419 section.azalea_write(buf)?;
420 }
421 Ok(())
422 }
423}
424
425impl Debug for PartialChunkStorage {
426 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
427 f.debug_struct("PartialChunkStorage")
428 .field("view_center", &self.view_center)
429 .field("chunk_radius", &self.chunk_radius)
430 .field("view_range", &self.view_range)
431 .field("chunks", &format_args!("{} items", self.chunks.len()))
433 .finish()
434 }
435}
436
437impl AzaleaRead for Section {
438 fn azalea_read(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> {
439 let block_count = u16::azalea_read(buf)?;
440
441 let states = PalettedContainer::read_with_type(buf, &PalettedContainerKind::BlockStates)?;
449
450 for i in 0..states.storage.size() {
451 if !BlockState::is_valid_state(states.storage.get(i) as BlockStateIntegerRepr) {
452 return Err(BufReadError::Custom(format!(
453 "Invalid block state {} (index {i}) found in section.",
454 states.storage.get(i)
455 )));
456 }
457 }
458
459 let biomes = PalettedContainer::read_with_type(buf, &PalettedContainerKind::Biomes)?;
460 Ok(Section {
461 block_count,
462 states,
463 biomes,
464 })
465 }
466}
467
468impl AzaleaWrite for Section {
469 fn azalea_write(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
470 self.block_count.azalea_write(buf)?;
471 self.states.azalea_write(buf)?;
472 self.biomes.azalea_write(buf)?;
473 Ok(())
474 }
475}
476
477impl Section {
478 pub fn get(&self, pos: ChunkSectionBlockPos) -> BlockState {
479 let state = self
481 .states
482 .get(pos.x as usize, pos.y as usize, pos.z as usize);
483 BlockState::try_from(state).unwrap_or(BlockState::AIR)
485 }
486
487 pub fn get_and_set(&mut self, pos: ChunkSectionBlockPos, state: BlockState) -> BlockState {
488 let previous_state =
489 self.states
490 .get_and_set(pos.x as usize, pos.y as usize, pos.z as usize, state.id);
491 BlockState::try_from(previous_state).unwrap_or(BlockState::AIR)
493 }
494
495 pub fn set(&mut self, pos: ChunkSectionBlockPos, state: BlockState) {
496 self.states
497 .set(pos.x as usize, pos.y as usize, pos.z as usize, state.id);
498 }
499}
500
501impl Default for PartialChunkStorage {
502 fn default() -> Self {
503 Self::new(8)
504 }
505}
506impl Default for ChunkStorage {
507 fn default() -> Self {
508 Self::new(384, -64)
509 }
510}
511
512#[inline]
515pub fn section_index(y: i32, min_y: i32) -> u32 {
516 if y < min_y {
517 #[cfg(debug_assertions)]
518 warn!("y ({y}) must be at least {min_y}");
519 #[cfg(not(debug_assertions))]
520 trace!("y ({y}) must be at least {min_y}")
521 };
522 let min_section_index = min_y >> 4;
523 ((y >> 4) - min_section_index) as u32
524}
525
526#[cfg(test)]
527mod tests {
528 use super::*;
529
530 #[test]
531 fn test_section_index() {
532 assert_eq!(section_index(0, 0), 0);
533 assert_eq!(section_index(128, 0), 8);
534 assert_eq!(section_index(127, 0), 7);
535 assert_eq!(section_index(0, -64), 4);
536 assert_eq!(section_index(-64, -64), 0);
537 assert_eq!(section_index(-49, -64), 0);
538 assert_eq!(section_index(-48, -64), 1);
539 assert_eq!(section_index(128, -64), 12);
540 }
541
542 #[test]
543 fn test_out_of_bounds_y() {
544 let mut chunk_storage = ChunkStorage::default();
545 let mut partial_chunk_storage = PartialChunkStorage::default();
546 partial_chunk_storage.set(
547 &ChunkPos { x: 0, z: 0 },
548 Some(Chunk::default()),
549 &mut chunk_storage,
550 );
551 assert!(
552 chunk_storage
553 .get_block_state(&BlockPos { x: 0, y: 319, z: 0 })
554 .is_some()
555 );
556 assert!(
557 chunk_storage
558 .get_block_state(&BlockPos { x: 0, y: 320, z: 0 })
559 .is_none()
560 );
561 assert!(
562 chunk_storage
563 .get_block_state(&BlockPos { x: 0, y: 338, z: 0 })
564 .is_none()
565 );
566 assert!(
567 chunk_storage
568 .get_block_state(&BlockPos { x: 0, y: -64, z: 0 })
569 .is_some()
570 );
571 assert!(
572 chunk_storage
573 .get_block_state(&BlockPos { x: 0, y: -65, z: 0 })
574 .is_none()
575 );
576 }
577
578 #[test]
579 fn test_chunk_pos_from_index() {
580 let mut partial_chunk_storage = PartialChunkStorage::new(5);
581 partial_chunk_storage.update_view_center(ChunkPos::new(0, -1));
582 assert_eq!(
583 partial_chunk_storage.chunk_pos_from_index(
584 partial_chunk_storage.index_from_chunk_pos(&ChunkPos::new(2, -1))
585 ),
586 ChunkPos::new(2, -1),
587 );
588 }
589}