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