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