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