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::{AzBuf, BufReadError};
15use azalea_core::{
16 heightmap_kind::HeightmapKind,
17 position::{
18 BlockPos, ChunkBiomePos, ChunkBlockPos, ChunkPos, ChunkSectionBiomePos,
19 ChunkSectionBlockPos,
20 },
21};
22use azalea_registry::data::Biome;
23use nohash_hasher::IntMap;
24use parking_lot::RwLock;
25use tracing::{debug, trace, warn};
26
27use crate::{heightmap::Heightmap, palette::PalettedContainer};
28
29const SECTION_HEIGHT: u32 = 16;
30
31pub struct PartialChunkStorage {
36 view_center: ChunkPos,
38 pub(crate) chunk_radius: u32,
39 view_range: u32,
40 chunks: Box<[Option<Arc<RwLock<Chunk>>>]>,
42}
43
44#[derive(Clone, Debug)]
52pub struct ChunkStorage {
53 pub height: u32,
58 pub min_y: i32,
64 pub map: IntMap<ChunkPos, Weak<RwLock<Chunk>>>,
65}
66
67#[derive(Debug)]
73pub struct Chunk {
74 pub sections: Box<[Section]>,
75 pub heightmaps: HashMap<HeightmapKind, Heightmap>,
79}
80
81#[derive(Clone, Debug, Default)]
83pub struct Section {
84 pub block_count: u16,
90 pub states: PalettedContainer<BlockState>,
91 pub biomes: PalettedContainer<Biome>,
92}
93
94pub fn calculate_chunk_storage_range(view_distance: u32) -> u32 {
98 u32::max(view_distance, 2) + 3
99}
100
101impl Default for Chunk {
102 fn default() -> Self {
103 Chunk {
104 sections: vec![Section::default(); (384 / 16) as usize].into(),
105 heightmaps: HashMap::new(),
106 }
107 }
108}
109
110impl PartialChunkStorage {
111 pub fn new(chunk_radius: u32) -> Self {
112 let view_range = chunk_radius * 2 + 1;
113 PartialChunkStorage {
114 view_center: ChunkPos::new(0, 0),
115 chunk_radius,
116 view_range,
117 chunks: vec![None; (view_range * view_range) as usize].into(),
118 }
119 }
120
121 pub fn update_view_center(&mut self, view_center: ChunkPos) {
126 self.view_center = view_center;
140 }
141
142 pub fn view_center(&self) -> ChunkPos {
145 self.view_center
146 }
147
148 pub fn view_range(&self) -> u32 {
149 self.view_range
150 }
151
152 pub fn index_from_chunk_pos(&self, chunk_pos: &ChunkPos) -> usize {
153 let view_range = self.view_range as i32;
154
155 let x = i32::rem_euclid(chunk_pos.x, view_range) * view_range;
156 let z = i32::rem_euclid(chunk_pos.z, view_range);
157 (x + z) as usize
158 }
159
160 pub fn chunk_pos_from_index(&self, index: usize) -> ChunkPos {
161 let view_range = self.view_range as i32;
162
163 let base_x = self.view_center.x.div_euclid(view_range) * view_range;
165 let base_z = self.view_center.z.div_euclid(view_range) * view_range;
166
167 let offset_x = index as i32 / view_range;
169 let offset_z = index as i32 % view_range;
170
171 ChunkPos::new(base_x + offset_x, base_z + offset_z)
172 }
173
174 pub fn in_range(&self, chunk_pos: &ChunkPos) -> bool {
175 in_range_for_view_center_and_radius(chunk_pos, self.view_center, self.chunk_radius)
176 }
177
178 pub fn set_block_state(
179 &self,
180 pos: BlockPos,
181 state: BlockState,
182 chunk_storage: &ChunkStorage,
183 ) -> Option<BlockState> {
184 if pos.y < chunk_storage.min_y
185 || pos.y >= (chunk_storage.min_y + chunk_storage.height as i32)
186 {
187 return None;
188 }
189 let chunk_pos = ChunkPos::from(pos);
190 let chunk_lock = chunk_storage.get(&chunk_pos)?;
191 let mut chunk = chunk_lock.write();
192 Some(chunk.get_and_set_block_state(&ChunkBlockPos::from(pos), state, chunk_storage.min_y))
193 }
194
195 pub fn replace_with_packet_data(
196 &mut self,
197 pos: &ChunkPos,
198 data: &mut Cursor<&[u8]>,
199 heightmaps: &[(HeightmapKind, Box<[u64]>)],
200 chunk_storage: &mut ChunkStorage,
201 ) -> Result<(), BufReadError> {
202 debug!("Replacing chunk at {:?}", pos);
203 if !self.in_range(pos) {
204 warn!("Ignoring chunk since it's not in the view range: {pos:?}");
205 return Ok(());
206 }
207
208 let chunk = Chunk::read_with_dimension_height(
209 data,
210 chunk_storage.height,
211 chunk_storage.min_y,
212 heightmaps,
213 )?;
214
215 self.set(pos, Some(chunk), chunk_storage);
216 trace!("Loaded chunk {pos:?}");
217
218 Ok(())
219 }
220
221 pub fn limited_get(&self, pos: &ChunkPos) -> Option<&Arc<RwLock<Chunk>>> {
224 if !self.in_range(pos) {
225 warn!(
226 "Chunk at {:?} is not in the render distance (center: {:?}, {} chunks)",
227 pos, self.view_center, self.chunk_radius,
228 );
229 return None;
230 }
231
232 let index = self.index_from_chunk_pos(pos);
233 self.chunks[index].as_ref()
234 }
235 pub fn limited_get_mut(&mut self, pos: &ChunkPos) -> Option<&mut Option<Arc<RwLock<Chunk>>>> {
240 if !self.in_range(pos) {
241 return None;
242 }
243
244 let index = self.index_from_chunk_pos(pos);
245
246 Some(&mut self.chunks[index])
247 }
248
249 pub fn set(&mut self, pos: &ChunkPos, chunk: Option<Chunk>, chunk_storage: &mut ChunkStorage) {
257 let new_chunk;
258
259 if let Some(chunk) = chunk {
261 match chunk_storage.map.entry(*pos) {
262 Entry::Occupied(mut e) => {
263 if let Some(old_chunk) = e.get_mut().upgrade() {
264 *old_chunk.write() = chunk;
265 new_chunk = Some(old_chunk);
266 } else {
267 let chunk_lock = Arc::new(RwLock::new(chunk));
268 e.insert(Arc::downgrade(&chunk_lock));
269 new_chunk = Some(chunk_lock);
270 }
271 }
272 Entry::Vacant(e) => {
273 let chunk_lock = Arc::new(RwLock::new(chunk));
274 e.insert(Arc::downgrade(&chunk_lock));
275 new_chunk = Some(chunk_lock);
276 }
277 }
278 } else {
279 new_chunk = None;
283 }
284
285 self.limited_set(pos, new_chunk);
286 }
287
288 pub fn limited_set(&mut self, pos: &ChunkPos, chunk: Option<Arc<RwLock<Chunk>>>) {
297 if let Some(chunk_mut) = self.limited_get_mut(pos) {
298 *chunk_mut = chunk;
299 }
300 }
301
302 pub fn chunks(&self) -> impl Iterator<Item = &Option<Arc<RwLock<Chunk>>>> {
304 self.chunks.iter()
305 }
306}
307impl ChunkStorage {
308 pub fn new(height: u32, min_y: i32) -> Self {
309 ChunkStorage {
310 height,
311 min_y,
312 map: IntMap::default(),
313 }
314 }
315
316 pub fn get(&self, pos: &ChunkPos) -> Option<Arc<RwLock<Chunk>>> {
317 self.map.get(pos).and_then(|chunk| chunk.upgrade())
318 }
319
320 pub fn get_block_state(&self, pos: BlockPos) -> Option<BlockState> {
321 let chunk_pos = ChunkPos::from(pos);
322 let chunk = self.get(&chunk_pos)?;
323 let chunk = chunk.read();
324 chunk.get_block_state(&ChunkBlockPos::from(pos), self.min_y)
325 }
326
327 pub fn get_fluid_state(&self, pos: BlockPos) -> Option<FluidState> {
328 let block_state = self.get_block_state(pos)?;
329 Some(FluidState::from(block_state))
330 }
331
332 pub fn get_biome(&self, pos: BlockPos) -> Option<Biome> {
333 let chunk_pos = ChunkPos::from(pos);
334 let chunk = self.get(&chunk_pos)?;
335 let chunk = chunk.read();
336 chunk.get_biome(ChunkBiomePos::from(pos), self.min_y)
337 }
338
339 pub fn set_block_state(&self, pos: BlockPos, state: BlockState) -> Option<BlockState> {
340 if pos.y < self.min_y || pos.y >= (self.min_y + self.height as i32) {
341 return None;
342 }
343 let chunk_pos = ChunkPos::from(pos);
344 let chunk = self.get(&chunk_pos)?;
345 let mut chunk = chunk.write();
346 Some(chunk.get_and_set_block_state(&ChunkBlockPos::from(pos), state, self.min_y))
347 }
348}
349
350pub fn in_range_for_view_center_and_radius(
351 chunk_pos: &ChunkPos,
352 view_center: ChunkPos,
353 chunk_radius: u32,
354) -> bool {
355 (chunk_pos.x - view_center.x).unsigned_abs() <= chunk_radius
356 && (chunk_pos.z - view_center.z).unsigned_abs() <= chunk_radius
357}
358
359impl Chunk {
360 pub fn read_with_dimension_height(
361 buf: &mut Cursor<&[u8]>,
362 dimension_height: u32,
363 min_y: i32,
364 heightmaps_data: &[(HeightmapKind, Box<[u64]>)],
365 ) -> Result<Self, BufReadError> {
366 let section_count = dimension_height / SECTION_HEIGHT;
367 let mut sections = Vec::with_capacity(section_count as usize);
368 for _ in 0..section_count {
369 let section = Section::azalea_read(buf)?;
370 sections.push(section);
371 }
372 let sections = sections.into_boxed_slice();
373
374 let mut heightmaps = HashMap::new();
375 for (kind, data) in heightmaps_data {
376 let data: Box<[u64]> = data.clone();
377 let heightmap = Heightmap::new(*kind, dimension_height, min_y, data);
378 heightmaps.insert(*kind, heightmap);
379 }
380
381 Ok(Chunk {
382 sections,
383 heightmaps,
384 })
385 }
386
387 pub fn get_block_state(&self, pos: &ChunkBlockPos, min_y: i32) -> Option<BlockState> {
388 get_block_state_from_sections(&self.sections, pos, min_y)
389 }
390
391 #[must_use = "Use Chunk::set_block_state instead if you don't need the previous state"]
392 pub fn get_and_set_block_state(
393 &mut self,
394 pos: &ChunkBlockPos,
395 state: BlockState,
396 min_y: i32,
397 ) -> BlockState {
398 let section_index = section_index(pos.y, min_y);
399 let Some(section) = self.sections.get_mut(section_index as usize) else {
400 warn!(
401 "Tried to get and set block state {state:?} at out-of-bounds relative chunk position {pos:?}",
402 );
403 return BlockState::AIR;
404 };
405 let chunk_section_pos = ChunkSectionBlockPos::from(pos);
406 let previous_state = section.get_and_set_block_state(chunk_section_pos, state);
407
408 for heightmap in self.heightmaps.values_mut() {
409 heightmap.update(pos, state, &self.sections);
410 }
411
412 previous_state
413 }
414
415 pub fn set_block_state(&mut self, pos: &ChunkBlockPos, state: BlockState, min_y: i32) {
416 let section_index = section_index(pos.y, min_y);
417 let Some(section) = self.sections.get_mut(section_index as usize) else {
418 warn!(
419 "Tried to set block state {state:?} at out-of-bounds relative chunk position {pos:?}",
420 );
421 return;
422 };
423 let chunk_section_pos = ChunkSectionBlockPos::from(pos);
424 section.set_block_state(chunk_section_pos, state);
425
426 for heightmap in self.heightmaps.values_mut() {
427 heightmap.update(pos, state, &self.sections);
428 }
429 }
430
431 pub fn get_biome(&self, pos: ChunkBiomePos, min_y: i32) -> Option<Biome> {
433 if pos.y < min_y {
434 return None;
436 }
437 let section_index = section_index(pos.y, min_y);
438 let Some(section) = self.sections.get(section_index as usize) else {
439 warn!("Tried to get biome at out-of-bounds relative chunk position {pos:?}",);
440 return None;
441 };
442 let chunk_section_pos = ChunkSectionBiomePos::from(pos);
443 Some(section.get_biome(chunk_section_pos))
444 }
445}
446
447#[inline]
450pub fn get_block_state_from_sections(
451 sections: &[Section],
452 pos: &ChunkBlockPos,
453 min_y: i32,
454) -> Option<BlockState> {
455 if pos.y < min_y {
456 return None;
458 }
459 let section_index = section_index(pos.y, min_y) as usize;
460 if section_index >= sections.len() {
461 return None;
463 };
464 let section = §ions[section_index];
465
466 let chunk_section_pos = ChunkSectionBlockPos::from(pos);
467 Some(section.get_block_state(chunk_section_pos))
468}
469
470impl Debug for PartialChunkStorage {
480 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
481 f.debug_struct("PartialChunkStorage")
482 .field("view_center", &self.view_center)
483 .field("chunk_radius", &self.chunk_radius)
484 .field("view_range", &self.view_range)
485 .field("chunks", &format_args!("{} items", self.chunks.len()))
487 .finish()
488 }
489}
490
491impl AzBuf for Section {
492 fn azalea_read(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> {
493 let block_count = u16::azalea_read(buf)?;
494
495 let states = PalettedContainer::<BlockState>::read(buf)?;
503
504 for i in 0..states.storage.size() {
505 if !BlockState::is_valid_state(states.storage.get(i) as BlockStateIntegerRepr) {
506 return Err(BufReadError::Custom(format!(
507 "Invalid block state {} (index {i}) found in section.",
508 states.storage.get(i)
509 )));
510 }
511 }
512
513 let biomes = PalettedContainer::<Biome>::read(buf)?;
514 Ok(Section {
515 block_count,
516 states,
517 biomes,
518 })
519 }
520 fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
521 self.block_count.azalea_write(buf)?;
522 self.states.write(buf)?;
523 self.biomes.write(buf)?;
524 Ok(())
525 }
526}
527
528impl Section {
529 pub fn get_block_state(&self, pos: ChunkSectionBlockPos) -> BlockState {
530 self.states.get(pos)
531 }
532 pub fn get_and_set_block_state(
533 &mut self,
534 pos: ChunkSectionBlockPos,
535 state: BlockState,
536 ) -> BlockState {
537 self.states.get_and_set(pos, state)
538 }
539 pub fn set_block_state(&mut self, pos: ChunkSectionBlockPos, state: BlockState) {
540 self.states.set(pos, state);
541 }
542
543 pub fn get_biome(&self, pos: ChunkSectionBiomePos) -> Biome {
544 self.biomes.get(pos)
545 }
546 pub fn set_biome(&mut self, pos: ChunkSectionBiomePos, biome: Biome) {
547 self.biomes.set(pos, biome);
548 }
549 pub fn get_and_set_biome(&mut self, pos: ChunkSectionBiomePos, biome: Biome) -> Biome {
550 self.biomes.get_and_set(pos, biome)
551 }
552}
553
554impl Default for PartialChunkStorage {
555 fn default() -> Self {
556 Self::new(8)
557 }
558}
559impl Default for ChunkStorage {
560 fn default() -> Self {
561 Self::new(384, -64)
562 }
563}
564
565#[inline]
568pub fn section_index(y: i32, min_y: i32) -> u32 {
569 if y < min_y {
570 #[cfg(debug_assertions)]
571 warn!("y ({y}) must be at least {min_y}");
572 #[cfg(not(debug_assertions))]
573 trace!("y ({y}) must be at least {min_y}")
574 };
575 let min_section_index = min_y >> 4;
576 ((y >> 4) - min_section_index) as u32
577}
578
579#[cfg(test)]
580mod tests {
581 use super::*;
582
583 #[test]
584 fn test_section_index() {
585 assert_eq!(section_index(0, 0), 0);
586 assert_eq!(section_index(128, 0), 8);
587 assert_eq!(section_index(127, 0), 7);
588 assert_eq!(section_index(0, -64), 4);
589 assert_eq!(section_index(-64, -64), 0);
590 assert_eq!(section_index(-49, -64), 0);
591 assert_eq!(section_index(-48, -64), 1);
592 assert_eq!(section_index(128, -64), 12);
593 }
594
595 #[test]
596 fn test_out_of_bounds_y() {
597 let mut chunk_storage = ChunkStorage::default();
598 let mut partial_chunk_storage = PartialChunkStorage::default();
599 partial_chunk_storage.set(
600 &ChunkPos { x: 0, z: 0 },
601 Some(Chunk::default()),
602 &mut chunk_storage,
603 );
604 assert!(
605 chunk_storage
606 .get_block_state(BlockPos { x: 0, y: 319, z: 0 })
607 .is_some()
608 );
609 assert!(
610 chunk_storage
611 .get_block_state(BlockPos { x: 0, y: 320, z: 0 })
612 .is_none()
613 );
614 assert!(
615 chunk_storage
616 .get_block_state(BlockPos { x: 0, y: 338, z: 0 })
617 .is_none()
618 );
619 assert!(
620 chunk_storage
621 .get_block_state(BlockPos { x: 0, y: -64, z: 0 })
622 .is_some()
623 );
624 assert!(
625 chunk_storage
626 .get_block_state(BlockPos { x: 0, y: -65, z: 0 })
627 .is_none()
628 );
629 }
630
631 #[test]
632 fn test_chunk_pos_from_index() {
633 let mut partial_chunk_storage = PartialChunkStorage::new(5);
634 partial_chunk_storage.update_view_center(ChunkPos::new(0, -1));
635 assert_eq!(
636 partial_chunk_storage.chunk_pos_from_index(
637 partial_chunk_storage.index_from_chunk_pos(&ChunkPos::new(2, -1))
638 ),
639 ChunkPos::new(2, -1),
640 );
641 }
642}