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