1use std::collections::HashSet;
2use std::time::Instant;
3use std::sync::Arc;
4
5use thiserror::Error;
6use hecs::Entity;
7
8use crate::util::{PackedArray, Palette, Rect, cast_vec_ref_to_ptr};
9use crate::heightmap::HeightmapType;
10use crate::block::BlockState;
11use crate::biome::Biome;
12use crate::perf;
13
14use super::level::LevelEnv;
15
16
17pub const SIZE: usize = 16;
19pub const BLOCKS_DATA_SIZE: usize = SIZE * SIZE * SIZE;
21pub const BIOMES_DATA_SIZE: usize = 4 * 4 * 4;
23
24
25static HEIGHTMAP_NON_NULL: HeightmapType = HeightmapType {
28 name: "INTERNAL_NON_NULL",
29 predicate: |state, blocks| blocks.get_state_from(0).unwrap() != state
30};
31
32
33#[derive(Error, Debug)]
36pub enum ChunkError {
37 #[error("You are trying to alter a sub chunk that is out of the height range.")]
38 SubChunkOutOfRange,
39 #[error("You are trying get information from an unloaded sub chunk.")]
40 SubChunkUnloaded,
41 #[error("You gave a block reference that is not supported by this chunk's level's env.")]
42 IllegalBlock,
43 #[error("You gave a biome reference that is not supported by this chunk's level's env.")]
44 IllegalBiome,
45 #[error("You gave a heightmap type that is not supported by this chunk's level's env.")]
46 IllegalHeightmap,
47 #[error("You are trying to access an unloaded chunk.")]
48 ChunkUnloaded, }
50
51pub type ChunkResult<T> = Result<T, ChunkError>;
53
54
55#[derive(Debug, Copy, Clone)]
58pub enum ChunkStatus {
59 Empty,
60 StructureStarts,
61 StructureReferences,
62 Biomes,
63 Noise,
64 Surface,
65 Carvers,
66 LiquidCarvers,
67 Features,
68 Light,
69 Spawn,
70 Heightmaps,
71 Full
72}
73
74
75pub struct Chunk {
80 cx: i32,
82 cz: i32,
84 env: Arc<LevelEnv>,
86 sub_chunks: Vec<Option<SubChunk>>,
88 sub_chunks_offset: i8,
90 biomes: PackedArray,
98 heightmaps: PackedArray,
101 status: ChunkStatus,
103 inhabited_time: u64,
106 entities: HashSet<Entity>,
108 last_save: Instant
110}
111
112impl Chunk {
113
114 pub(super) fn new(env: Arc<LevelEnv>, height: ChunkHeight, cx: i32, cz: i32) -> Self {
115
116 let biomes_byte_size = PackedArray::calc_min_byte_size(env.biomes.biomes_count() as u64 - 1);
119
120 let heightmap_byte_size = PackedArray::calc_min_byte_size((height.len() * 16) as u64);
121 let heightmap_len = env.heightmaps.heightmaps_count() * 256 + 256;
123
124 Chunk {
125 cx,
126 cz,
127 env,
128 status: ChunkStatus::Empty,
129 sub_chunks: (0..height.len()).map(|_| None).collect(),
130 sub_chunks_offset: height.min,
131 biomes: PackedArray::new(height.len() * 64, biomes_byte_size, None),
132 heightmaps: PackedArray::new(heightmap_len, heightmap_byte_size, None),
133 inhabited_time: 0,
134 entities: HashSet::new(),
135 last_save: Instant::now()
136 }
137
138 }
139
140 #[inline]
142 pub fn get_position(&self) -> (i32, i32) {
143 (self.cx, self.cz)
144 }
145
146 #[inline]
147 pub fn get_env(&self) -> &Arc<LevelEnv> {
148 &self.env
149 }
150
151 #[inline]
152 pub fn get_status(&self) -> ChunkStatus {
153 self.status
154 }
155
156 #[inline]
157 pub fn set_status(&mut self, status: ChunkStatus) {
158 self.status = status;
159 }
160
161 #[inline]
162 pub fn get_inhabited_time(&self) -> u64 {
163 self.inhabited_time
164 }
165
166 #[inline]
167 pub fn set_inhabited_time(&mut self, time: u64) {
168 self.inhabited_time = time;
169 }
170
171 #[inline]
172 pub fn get_last_save(&self) -> Instant {
173 self.last_save
174 }
175
176 #[inline]
177 pub fn update_last_save(&mut self) {
178 self.last_save = Instant::now();
179 }
180
181 #[inline]
185 pub fn get_height(&self) -> ChunkHeight {
186 ChunkHeight {
187 min: self.sub_chunks_offset,
188 max: self.sub_chunks_offset + self.sub_chunks.len() as i8 - 1
189 }
190 }
191
192 fn calc_sub_chunk_offset(&self, cy: i8) -> Option<usize> {
198 cy.checked_sub(self.sub_chunks_offset).map(|v| v as usize)
199 }
200
201 pub fn get_sub_chunk_offset(&self, cy: i8) -> Option<usize> {
205 match self.calc_sub_chunk_offset(cy) {
206 Some(off) if off < self.sub_chunks.len() => Some(off),
207 _ => None
208 }
209 }
210
211 pub fn ensure_sub_chunk(&mut self, cy: i8) -> ChunkResult<&mut SubChunk> {
216
217 let offset = self.calc_sub_chunk_offset(cy).ok_or(ChunkError::SubChunkOutOfRange)?;
218
219 match self.sub_chunks.get_mut(offset) {
220 Some(Some(sub_chunk)) => Ok(sub_chunk),
221 Some(sub_chunk ) => {
222 Ok(sub_chunk.insert(SubChunk::new(Arc::clone(&self.env))))
223 },
224 None => Err(ChunkError::SubChunkOutOfRange)
225 }
226
227 }
228
229 #[inline]
230 pub fn ensure_sub_chunk_at(&mut self, y: i32) -> ChunkResult<&mut SubChunk> {
231 self.ensure_sub_chunk((y >> 4) as i8)
232 }
233
234 pub fn replace_sub_chunk(&mut self, cy: i8, sub_chunk: SubChunk) -> ChunkResult<&mut SubChunk> {
239 debug_assert!(Arc::ptr_eq(&self.env, &sub_chunk.env));
240 let offset = self.calc_sub_chunk_offset(cy).ok_or(ChunkError::SubChunkOutOfRange)?;
241 let container = self.sub_chunks.get_mut(offset).ok_or(ChunkError::SubChunkOutOfRange)?;
242 Ok(container.insert(sub_chunk))
243 }
244
245 pub fn get_sub_chunk(&self, cy: i8) -> Option<&SubChunk> {
247 let offset = self.calc_sub_chunk_offset(cy)?;
248 match self.sub_chunks.get(offset) {
249 Some(Some(chunk)) => Some(chunk),
250 _ => None
251 }
252 }
253
254 #[inline]
255 pub fn get_sub_chunk_at(&self, y: i32) -> Option<&SubChunk> {
256 self.get_sub_chunk((y >> 4) as i8)
257 }
258
259 pub fn get_sub_chunk_mut(&mut self, cy: i8) -> Option<&mut SubChunk> {
261 let offset = self.calc_sub_chunk_offset(cy)?;
262 match self.sub_chunks.get_mut(offset) {
263 Some(Some(chunk)) => Some(chunk),
264 _ => None
265 }
266 }
267
268 #[inline]
269 pub fn get_sub_chunk_at_mut(&mut self, y: i32) -> Option<&mut SubChunk> {
270 self.get_sub_chunk_mut((y >> 4) as i8)
271 }
272
273
274 pub fn get_sub_chunks_count(&self) -> usize {
276 self.sub_chunks.len()
277 }
278
279 pub fn iter_sub_chunks(&self) -> impl Iterator<Item = (i8, Option<&'_ SubChunk>)> + '_ {
281 let min_y = self.sub_chunks_offset;
282 self.sub_chunks.iter()
283 .enumerate()
284 .map(move |(idx, opt)| {
285 (idx as i8 + min_y, opt.as_ref())
286 })
287 }
288
289 pub fn iter_loaded_sub_chunks(&self) -> impl Iterator<Item = (i8, &'_ SubChunk)> + '_ {
291 let min_y = self.sub_chunks_offset;
292 self.sub_chunks.iter()
293 .enumerate()
294 .filter_map(move |(idx, opt)| {
295 opt.as_ref().map(move |sc| (idx as i8 + min_y, sc))
296 })
297 }
298
299 pub fn get_highest_non_null_sub_chunk(&self) -> i8 {
302 self.sub_chunks.iter()
303 .rposition(|sc| {
304 match sc {
305 Some(sc) => sc.has_non_null_block(),
306 None => false
307 }
308 }).unwrap_or(0) as i8 + self.sub_chunks_offset
309 }
310
311 pub fn get_block(&self, x: u8, y: i32, z: u8) -> ChunkResult<&'static BlockState> {
323
324 let offset = self.calc_sub_chunk_offset((y >> 4) as i8)
325 .ok_or(ChunkError::SubChunkOutOfRange)?;
326
327 match self.sub_chunks.get(offset) {
328 Some(Some(sub_chunk)) => Ok(sub_chunk.get_block(x, (y & 15) as u8, z)),
329 Some(None) => Ok(self.env.blocks.get_state_from(0).unwrap()),
330 _ => Err(ChunkError::SubChunkOutOfRange)
331 }
332
333
334 }
340
341 #[inline]
345 pub fn get_block_at(&self, x: i32, y: i32, z: i32) -> ChunkResult<&'static BlockState> {
346 self.get_block((x & 15) as u8, y, (z & 15) as u8)
347 }
348
349 pub fn set_block(&mut self, x: u8, y: i32, z: u8, state: &'static BlockState) -> ChunkResult<()> {
358 let sub_chunk = self.ensure_sub_chunk_at(y)?;
359 match sub_chunk.set_block(x, (y & 15) as u8, z, state) {
360 Ok(()) => {
361 self.update_heightmap_column(x, y, z, state);
362 Ok(())
363 },
364 e => e
365 }
366 }
367
368 #[inline]
372 pub fn set_block_at(&mut self, x: i32, y: i32, z: i32, state: &'static BlockState) -> ChunkResult<()> {
373 self.set_block((x & 15) as u8, y, (z & 15) as u8, state)
374 }
375
376 fn calc_biome_offset(&self, x: u8, y: i32, z: u8) -> usize {
379 calc_biome_index(x, (y - self.sub_chunks_offset as i32 * 4) as usize, z)
380 }
381
382 pub fn get_biome(&self, x: u8, y: i32, z: u8) -> ChunkResult<&'static Biome> {
393 let offset = self.calc_biome_offset(x, y, z);
394 let sid = self.biomes.get(offset).ok_or(ChunkError::SubChunkOutOfRange)? as u16;
395 Ok(self.env.biomes.get_biome_from(sid).unwrap())
396 }
397
398 #[inline]
399 pub fn get_biome_at(&self, x: i32, y: i32, z: i32) -> ChunkResult<&'static Biome> {
400 self.get_biome(((x >> 2) & 3) as u8, y >> 2, ((z >> 2) & 3) as u8)
401 }
402
403 pub fn set_biome(&mut self, x: u8, y: i32, z: u8, biome: &'static Biome) -> ChunkResult<()> {
414 let offset = self.calc_biome_offset(x, y, z);
415 let sid = self.env.biomes.get_sid_from(biome).ok_or(ChunkError::IllegalBiome)?;
416 if offset < self.biomes.len() {
417 self.biomes.set(offset, sid as u64);
418 Ok(())
419 } else {
420 Err(ChunkError::SubChunkOutOfRange)
421 }
422 }
423
424 #[inline]
425 pub fn set_biome_at(&mut self, x: i32, y: i32, z: i32, biome: &'static Biome) -> ChunkResult<()> {
426 self.set_biome(((x >> 2) & 3) as u8, y >> 2, ((z >> 2) & 3) as u8, biome)
427 }
428
429 pub fn set_biomes_2d(&mut self, biomes: &Rect<&'static Biome>) -> ChunkResult<()> {
433
434 assert!(biomes.x_size >= 16 && biomes.z_size >= 16, "Given biomes rectangle is too small.");
435
436 let mut layer_biomes = [0; 16];
437
438 for z in 0..4 {
439 for x in 0..4 {
440 let idx = x + z * 4;
441 let biome = biomes.data[idx * 4];
442 layer_biomes[idx] = self.env.biomes.get_sid_from(biome).ok_or(ChunkError::IllegalBiome)?;
443 }
444 }
445
446 self.biomes.replace(move |i, _| layer_biomes[i % 16] as u64);
447 Ok(())
448
449 }
450
451 pub fn set_biomes_3d(&mut self, biomes: &[&'static Biome]) -> ChunkResult<()> {
452 assert_eq!(biomes.len(), self.sub_chunks.len() * 64, "Given biomes array must be {} biomes long.", self.sub_chunks.len() * 64);
453 let env_biomes = &self.env.biomes;
454 self.biomes.replace(move |i, old| {
455 env_biomes.get_sid_from(biomes[i]).map(|v| v as u64).unwrap_or(old)
456 });
457 Ok(())
458 }
459
460 pub fn get_biomes_count(&self) -> usize {
461 self.biomes.len()
462 }
463
464 pub fn iter_biomes(&self) -> impl Iterator<Item = &'static Biome> + '_ {
467 let biomes = &self.env.biomes;
468 self.biomes.iter().map(move |v| biomes.get_biome_from(v as u16).unwrap())
469 }
470
471 fn get_heightmap_column_index(&self, heightmap_type: &'static HeightmapType, x: u8, z: u8) -> ChunkResult<usize> {
476 self.env.heightmaps.get_heightmap_index(heightmap_type)
477 .map(move |offset| 256 + offset * 256 + calc_heightmap_index(x, z))
478 .ok_or(ChunkError::IllegalHeightmap)
479 }
480
481 pub fn set_heightmap_column(&mut self, heightmap_type: &'static HeightmapType, x: u8, z: u8, y: i32) -> ChunkResult<()> {
484 let column_index = self.get_heightmap_column_index(heightmap_type, x, z)?;
485 self.heightmaps.set(column_index, (y - self.get_height().get_min_block()) as u64);
486 Ok(())
487 }
488
489 #[inline]
490 pub fn set_heightmap_column_at(&mut self, heightmap_type: &'static HeightmapType, x: i32, z: i32, y: i32) -> ChunkResult<()> {
491 self.set_heightmap_column(heightmap_type, (x & 15) as u8, (z & 15) as u8, y)
492 }
493
494 pub fn get_heightmap_column(&self, heightmap_type: &'static HeightmapType, x: u8, z: u8) -> ChunkResult<i32> {
496 let column_index = self.get_heightmap_column_index(heightmap_type, x, z)?;
497 Ok(self.heightmaps.get(column_index).unwrap() as i32 + self.get_height().get_min_block())
500 }
501
502 #[inline]
503 pub fn get_heightmap_column_at(&self, heightmap_type: &'static HeightmapType, x: i32, z: i32) -> ChunkResult<i32> {
504 self.get_heightmap_column(heightmap_type, (x & 15) as u8, (z & 15) as u8)
505 }
506
507 fn update_heightmap_column(&mut self, x: u8, y: i32, z: u8, state: &'static BlockState) {
510
511 let min_block_y = self.get_height().get_min_block();
512 let column_index = calc_heightmap_index(x, z);
513
514 for (idx, heightmap_type) in std::iter::once(&HEIGHTMAP_NON_NULL)
515 .chain(self.env.heightmaps.iter_heightmap_types())
516 .enumerate()
517 {
518 let column_index = idx * 256 + column_index;
519 let current_y = self.heightmaps.get(column_index).unwrap() as i32 + min_block_y;
520 if heightmap_type.check_block(state, &self.env.blocks) {
521 if y >= current_y {
523 self.heightmaps.set(column_index, (y + 1 - min_block_y) as u64);
524 }
525 } else {
526 if y + 1 == current_y {
529 let height = self.recompute_heightmap_column_internal(heightmap_type, x, z, y - 1);
530 self.heightmaps.set(column_index, height);
531 }
532 }
533 }
534
535 }
536
537 pub fn recompute_heightmap_column(&mut self, x: u8, z: u8) {
539
540 perf::push("Chunk::recompute_heightmap_column");
541
542 let min_block_y = self.get_height().get_min_block();
543 let max_block_y = self.get_height().get_max_block();
544 let column_index = calc_heightmap_index(x, z);
545
546 perf::push("non_null_heightmap");
547 let non_null_height = self.recompute_heightmap_column_internal(&HEIGHTMAP_NON_NULL, x, z, max_block_y);
548 self.heightmaps.set(column_index, non_null_height);
549 perf::pop();
550
551 let from_y = non_null_height as i32 + min_block_y - 1;
553
554 for (idx, heightmap_type) in self.env.heightmaps.iter_heightmap_types().enumerate() {
555 perf::push("heightmap_type");
556 let column_index = 256 + idx * 256 + column_index;
557 let height = self.recompute_heightmap_column_internal(heightmap_type, x, z, from_y);
558 self.heightmaps.set(column_index, height);
559 perf::pop();
560 }
561
562 perf::pop();
563
564 }
565
566 fn recompute_heightmap_column_internal(&self, heightmap_type: &'static HeightmapType, x: u8, z: u8, from_y: i32) -> u64 {
570
571 perf::push("Chunk::recompute_heightmap_column_internal");
572
573 let sub_chunk_index = (from_y >> 4) as i8 - self.sub_chunks_offset;
574 if sub_chunk_index < 0 {
575 perf::pop();
576 return 0; }
578
579 let mut sub_chunk_index = sub_chunk_index as usize;
580 let mut by = (from_y & 15) as u8 + 1;
581
582 loop {
583
584 let sub_chunk = loop {
585 match self.sub_chunks[sub_chunk_index] {
586 Some(ref sub_chunk) if sub_chunk.has_non_null_block() => break sub_chunk,
587 _ => {
588 if sub_chunk_index == 0 {
589 perf::pop();
590 return 0; } else {
592 sub_chunk_index -= 1;
593 }
594 }
595 }
596 };
597
598 while by > 0 {
599 by -= 1;
600 perf::push("check_block");
601 let state = sub_chunk.get_block(x, by, z);
602 if heightmap_type.check_block(state, &self.env.blocks) {
603 perf::pop();
604 perf::pop();
605 return (sub_chunk_index << 4) as u64 + by as u64 + 1;
606 }
607 perf::pop();
608 }
609
610 if sub_chunk_index == 0 {
611 perf::pop();
612 return 0; } else {
614 sub_chunk_index -= 1;
615 by = 16;
616 }
617
618 }
619
620 }
621
622 pub fn iter_heightmap_raw_columns(&self, heightmap_type: &'static HeightmapType) -> Option<(u8, impl Iterator<Item = u64> + '_)> {
625 let offset = 256 + self.env.heightmaps.get_heightmap_index(heightmap_type)? * 256;
626 Some((self.heightmaps.byte_size(), self.heightmaps.iter().skip(offset).take(256)))
627 }
628
629 #[inline]
632 pub unsafe fn add_entity_unchecked(&mut self, entity: Entity) {
633 self.entities.insert(entity);
634 }
635
636 #[inline]
637 pub unsafe fn remove_entity_unchecked(&mut self, entity: Entity) {
638 self.entities.remove(&entity);
639 }
640
641 #[inline]
642 pub fn has_entity(&self, entity: Entity) -> bool {
643 self.entities.contains(&entity)
644 }
645
646}
647
648
649enum SubChunkBlocks {
651 Local {
653 palette: Palette<*const BlockState>,
660 null_block_sid: u32
664 },
665 Global
667}
668
669impl SubChunkBlocks {
670
671 fn get_null_block_sid(&self) -> Option<u32> {
673 match self {
674 Self::Local { null_block_sid: u32::MAX, .. } => None,
675 Self::Local { null_block_sid, .. } => Some(*null_block_sid),
676 Self::Global => Some(0)
677 }
678 }
679
680}
681
682unsafe impl Send for SubChunkBlocks {}
684unsafe impl Sync for SubChunkBlocks {}
685
686
687pub struct SubChunk {
689 env: Arc<LevelEnv>,
691 blocks_palette: SubChunkBlocks,
693 blocks: PackedArray,
695 non_null_blocks_count: u16,
699}
700
701const BLOCKS_PALETTE_CAPACITY: usize = 128;
704
705const BLOCKS_ARRAY_MIN_BYTE_SIZE: u8 = 4;
707
708
709impl SubChunk {
710
711 pub fn new(env: Arc<LevelEnv>) -> Self {
715
716 let null_block = env.blocks.get_state_from(0)
717 .expect("An global blocks palette is not supported by SubChunk.");
718
719 SubChunk {
723 env,
724 blocks_palette: SubChunkBlocks::Local {
725 palette: Palette::with_default(null_block, BLOCKS_PALETTE_CAPACITY),
726 null_block_sid: 0
727 },
728 blocks: PackedArray::new(BLOCKS_DATA_SIZE, BLOCKS_ARRAY_MIN_BYTE_SIZE, None),
729 non_null_blocks_count: 0,
730 }
731
732 }
733
734 #[inline]
735 fn get_blocks_byte_size(env: &LevelEnv) -> u8 {
736 PackedArray::calc_min_byte_size(env.blocks.states_count() as u64 - 1)
737 .max(BLOCKS_ARRAY_MIN_BYTE_SIZE)
738 }
739
740 pub fn get_block(&self, x: u8, y: u8, z: u8) -> &'static BlockState {
743 let sid = self.blocks.get(calc_block_index(x, y, z)).unwrap() as u32;
746 self.get_block_from_sid(sid)
747 }
748
749 pub fn set_block(&mut self, x: u8, y: u8, z: u8, state: &'static BlockState) -> ChunkResult<()> {
752 let idx = calc_block_index(x, y, z);
753 match self.ensure_block_sid(state) {
754 Some(sid) => {
755 let old_sid = self.blocks.set(idx, sid as u64) as u32;
756 if let Some(null_block_sid) = self.blocks_palette.get_null_block_sid() {
757 let was_null = old_sid == null_block_sid;
758 let is_null = sid == null_block_sid;
759 self.non_null_blocks_count = (self.non_null_blocks_count as i16 + was_null as i16 - is_null as i16) as u16;
760 }
761 Ok(())
762 },
763 None => Err(ChunkError::IllegalBlock)
764 }
765 }
766
767 #[inline]
770 fn get_block_from_sid(&self, sid: u32) -> &'static BlockState {
771 match self.blocks_palette {
776 SubChunkBlocks::Local { ref palette, .. } => unsafe {
777 std::mem::transmute(palette.get_item(sid as usize).unwrap())
779 },
780 SubChunkBlocks::Global => self.env.blocks.get_state_from(sid).unwrap()
781 }
782 }
783
784 fn ensure_block_sid(&mut self, state: &'static BlockState) -> Option<u32> {
789
790 if let SubChunkBlocks::Local {
791 ref mut palette,
792 ..
793 } = self.blocks_palette {
794
795 let state_ptr = state as *const BlockState;
796
797 match palette.search_index(state_ptr) {
801 Some(sid) => return Some(sid as u32),
802 None => {
803 if self.env.blocks.has_state(state) {
804 match palette.insert_index(state_ptr) {
805 Some(sid) => {
806 if sid as u64 > self.blocks.max_value() {
807 self.blocks.resize_byte(self.blocks.byte_size() + 1);
808 }
809 return Some(sid as u32);
810 },
811 None => {
812 }
815 }
816 } else {
817 return None;
818 }
819 }
820 }
821
822 self.use_global_blocks();
823
824 }
825
826 self.env.blocks.get_sid_from(state)
827
828 }
829
830 fn use_global_blocks(&mut self) {
831 if let SubChunkBlocks::Local {
832 ref palette,
833 ..
834 } = self.blocks_palette {
835 let new_byte_size = Self::get_blocks_byte_size(&self.env);
836 let global_palette = &self.env.blocks;
837 self.blocks.resize_byte_and_replace(new_byte_size, move |_, sid| {
838 let state = palette.get_item(sid as usize).unwrap();
839 global_palette.get_sid_from(unsafe { std::mem::transmute(state) }).unwrap() as u64
840 });
841 self.blocks_palette = SubChunkBlocks::Global;
842 }
843 }
844
845 pub fn fill_block(&mut self, state: &'static BlockState) -> ChunkResult<()> {
847
848 let null_block_sid = match self.env.blocks.get_sid_from(state) {
849 None => return Err(ChunkError::IllegalBlock),
850 Some(0) => {
851 self.non_null_blocks_count = 0;
852 0
853 }
854 Some(_) => {
855 self.non_null_blocks_count = 4096;
856 u32::MAX
857 }
858 };
859
860 self.blocks_palette = SubChunkBlocks::Local {
861 palette: Palette::with_default(state, BLOCKS_PALETTE_CAPACITY),
862 null_block_sid
863 };
864
865 unsafe {
866 self.blocks.resize_raw(BLOCKS_ARRAY_MIN_BYTE_SIZE);
869 self.blocks.clear_cells();
870 }
871
872 Ok(())
873
874 }
875
876 pub unsafe fn set_blocks_raw<I>(&mut self, palette: Vec<&'static BlockState>, mut blocks: I)
884 where
885 I: Iterator<Item = usize>
886 {
887
888 assert_ne!(palette.len(), 0, "Palette length is zero.");
889
890 if palette.len() <= BLOCKS_PALETTE_CAPACITY {
891
892 let palette = cast_vec_ref_to_ptr(palette);
893 let byte_size = PackedArray::calc_min_byte_size(palette.len() as u64 - 1)
894 .max(BLOCKS_ARRAY_MIN_BYTE_SIZE);
895
896 self.blocks_palette = SubChunkBlocks::Local {
897 null_block_sid: {
898 let null_block = self.env.blocks.get_state_from(0).unwrap();
899 palette.iter()
900 .position(move |&v| v == null_block)
901 .map(|idx| idx as u32)
902 .unwrap_or(u32::MAX)
903 },
904 palette: Palette::from_raw(palette, BLOCKS_PALETTE_CAPACITY)
905 };
906
907 self.blocks.resize_raw(byte_size);
910 self.blocks.replace(move |_, _| {
911 blocks.next().unwrap_or(0) as u64
912 });
913
914 } else {
915
916 self.blocks_palette = SubChunkBlocks::Global;
917
918 let byte_size = Self::get_blocks_byte_size(&self.env);
919 let global_blocks = &self.env.blocks;
920
921 self.blocks.resize_raw(byte_size);
922 self.blocks.replace(move |_, _| {
923 let palette_idx = blocks.next().unwrap_or(0);
924 let state = palette[palette_idx];
925 global_blocks.get_sid_from(state).unwrap() as u64
927 });
928
929 }
930
931 self.refresh_non_null_blocks_count();
932
933 }
934
935 pub fn iter_blocks(&self) -> impl Iterator<Item = &'static BlockState> + '_ {
937 SubChunkBlocksIter {
938 inner: self.blocks.iter(),
939 sub_chunk: self,
940 last_block: None
941 }
942 }
943
944 fn refresh_non_null_blocks_count(&mut self) {
945 if let Some(null_block_sid) = self.blocks_palette.get_null_block_sid() {
946 self.non_null_blocks_count = self.blocks.iter()
947 .filter(move |&sid| sid != null_block_sid as u64)
948 .count() as u16;
949 } else {
950 self.non_null_blocks_count = 4096; }
952 }
953
954 #[inline]
957 pub fn non_null_blocks_count(&self) -> u16 {
958 self.non_null_blocks_count
959 }
960
961 #[inline]
962 pub fn null_blocks_count(&self) -> u16 {
963 4096 - self.non_null_blocks_count
964 }
965
966 #[inline]
967 pub fn has_non_null_block(&self) -> bool {
968 self.non_null_blocks_count != 0
969 }
970
971}
972
973
974pub struct SubChunkBlocksIter<'a, I> {
976 inner: I,
977 sub_chunk: &'a SubChunk,
978 last_block: Option<(u64, &'static BlockState)>
979}
980
981impl<'a, I> Iterator for SubChunkBlocksIter<'a, I>
982where
983 I: Iterator<Item = u64>
984{
985
986 type Item = &'static BlockState;
987
988 fn next(&mut self) -> Option<Self::Item> {
989 match self.inner.next() {
990 None => None,
991 Some(sid) => {
992 match self.last_block {
993 Some((last_sid, state)) if last_sid == sid => Some(state),
994 ref mut last_block => {
995 Some(&last_block.insert((sid, self.sub_chunk.get_block_from_sid(sid as u32))).1)
996 }
997 }
998 }
999 }
1000 }
1001
1002}
1003
1004
1005#[derive(Debug, Clone, Copy)]
1007pub struct ChunkHeight {
1008 pub min: i8,
1010 pub max: i8,
1012}
1013
1014impl ChunkHeight {
1015
1016 pub fn new(min: i8, max: i8) -> Self {
1017 Self { min, max }
1018 }
1019
1020 #[inline]
1022 pub fn get_min_block(self) -> i32 {
1023 (self.min as i32) * 16
1024 }
1025
1026 #[inline]
1028 pub fn get_max_block(self) -> i32 {
1029 (self.max as i32) * 16 + 15
1030 }
1031
1032 #[inline]
1034 pub fn contains(self, cy: i8) -> bool {
1035 return self.min <= cy && cy <= self.max;
1036 }
1037
1038 #[inline]
1040 pub fn len(self) -> usize {
1041 (self.max - self.min + 1) as usize
1042 }
1043
1044 #[inline]
1046 pub fn iter(self) -> impl Iterator<Item = i8> {
1047 self.min..=self.max
1048 }
1049
1050}
1051
1052
1053#[inline]
1054fn calc_block_index(x: u8, y: u8, z: u8) -> usize {
1055 debug_assert!(x < 16 && y < 16 && z < 16, "x: {}, y: {}, z: {}", x, y, z);
1056 x as usize | ((z as usize) << 4) | ((y as usize) << 8)
1057}
1058
1059
1060#[inline]
1061fn calc_biome_index(x: u8, y: usize, z: u8) -> usize {
1062 debug_assert!(x < 4 && z < 4, "x: {}, z: {}", x, z);
1063 x as usize | ((z as usize) << 2) | (y << 4)
1064}
1065
1066
1067#[inline]
1068fn calc_heightmap_index(x: u8, z: u8) -> usize {
1069 debug_assert!(x < 16 && z < 16, "x: {}, z: {}", x, z);
1070 x as usize | ((z as usize) << 4)
1071}
1072
1073
1074#[cfg(test)]
1075mod tests {
1076
1077 use super::*;
1078 use crate::block::GlobalBlocks;
1079 use crate::biome::GlobalBiomes;
1080 use crate::entity::GlobalEntities;
1081 use crate::heightmap::GlobalHeightmaps;
1082
1083 crate::blocks!(TEST_BLOCKS "test" [
1084 AIR "air",
1085 STONE "stone",
1086 DIRT "dirt"
1087 ]);
1088
1089 crate::biomes!(TEST_BIOMES "test" [
1090 VOID "void" 0,
1091 ]);
1092
1093 fn heightmap_test(state: &'static BlockState, _blocks: &GlobalBlocks) -> bool {
1094 state == STONE.get_default_state()
1095 }
1096
1097 crate::heightmaps!(TEST_HEIGHTMAPS [
1098 TEST "TEST" heightmap_test
1099 ]);
1100
1101 fn build_chunk() -> Chunk {
1102 let env = Arc::new(LevelEnv::new(
1103 GlobalBlocks::with_all(&TEST_BLOCKS).unwrap(),
1104 GlobalBiomes::with_all(&TEST_BIOMES).unwrap(),
1105 GlobalEntities::new(),
1106 GlobalHeightmaps::with_all(&TEST_HEIGHTMAPS)
1107 ));
1108 Chunk::new(env, ChunkHeight::new(-1, 2), 0, 0)
1109 }
1110
1111 #[test]
1112 fn valid_height() {
1113 let mut chunk = build_chunk();
1114 assert!(matches!(chunk.ensure_sub_chunk(-2), Err(ChunkError::SubChunkOutOfRange)));
1115 assert!(matches!(chunk.ensure_sub_chunk(-1), Ok(_)));
1116 assert!(matches!(chunk.ensure_sub_chunk(0), Ok(_)));
1117 assert!(matches!(chunk.ensure_sub_chunk(1), Ok(_)));
1118 assert!(matches!(chunk.ensure_sub_chunk(2), Ok(_)));
1119 assert!(matches!(chunk.ensure_sub_chunk(3), Err(ChunkError::SubChunkOutOfRange)));
1120 }
1121
1122 #[test]
1123 fn valid_set_get() {
1124 let mut chunk = build_chunk();
1125 chunk.ensure_sub_chunk(-1).unwrap();
1126 assert_eq!(chunk.get_block(0, -16, 0).unwrap(), AIR.get_default_state());
1127 assert_eq!(chunk.get_block(0, 0, 0).unwrap(), AIR.get_default_state());
1128 assert!(matches!(chunk.get_block(0, -17, 0), Err(ChunkError::SubChunkOutOfRange)));
1129 assert!(matches!(chunk.set_block(0, 0, 0, STONE.get_default_state()), Ok(_)));
1130 assert_eq!(chunk.get_block(0, 0, 0).unwrap(), STONE.get_default_state());
1131 }
1132
1133 #[test]
1134 fn valid_heightmap() {
1135 let mut chunk = build_chunk();
1136 assert!(matches!(chunk.get_heightmap_column(&TEST, 0, 0), Ok(-16)));
1137 chunk.set_block(0, -16, 0, STONE.get_default_state()).unwrap();
1138 assert!(matches!(chunk.get_heightmap_column(&TEST, 0, 0), Ok(-15)));
1139 chunk.set_block(0, 0, 0, STONE.get_default_state()).unwrap();
1140 chunk.set_block(0, -1, 0, STONE.get_default_state()).unwrap();
1141 assert!(matches!(chunk.get_heightmap_column(&TEST, 0, 0), Ok(1)));
1142 chunk.set_block(0, 0, 0, AIR.get_default_state()).unwrap();
1143 assert!(matches!(chunk.get_heightmap_column(&TEST, 0, 0), Ok(0)));
1144 chunk.set_block(0, -1, 0, AIR.get_default_state()).unwrap();
1145 assert!(matches!(chunk.get_heightmap_column(&TEST, 0, 0), Ok(-15)));
1146 chunk.set_block(0, -16, 0, AIR.get_default_state()).unwrap();
1147 assert!(matches!(chunk.get_heightmap_column(&TEST, 0, 0), Ok(-16)));
1148 chunk.set_block(0, 47, 0, STONE.get_default_state()).unwrap();
1149 assert!(matches!(chunk.get_heightmap_column(&TEST, 0, 0), Ok(48)));
1150 }
1151
1152 #[test]
1153 fn valid_non_null_blocks_count() {
1154
1155 let mut chunk = build_chunk();
1156 let sub_chunk = chunk.ensure_sub_chunk(0).unwrap();
1157 assert_eq!(sub_chunk.null_blocks_count(), 4096);
1158 assert_eq!(sub_chunk.non_null_blocks_count(), 0);
1159 sub_chunk.set_block(0, 0, 0, STONE.get_default_state()).unwrap();
1160 assert_eq!(sub_chunk.non_null_blocks_count(), 1);
1161 sub_chunk.set_block(0, 1, 0, STONE.get_default_state()).unwrap();
1162 assert_eq!(sub_chunk.non_null_blocks_count(), 2);
1163 sub_chunk.set_block(0, 1, 0, DIRT.get_default_state()).unwrap();
1164 assert_eq!(sub_chunk.non_null_blocks_count(), 2);
1165 sub_chunk.set_block(0, 0, 0, AIR.get_default_state()).unwrap();
1166 assert_eq!(sub_chunk.non_null_blocks_count(), 1);
1167
1168 sub_chunk.fill_block(AIR.get_default_state()).unwrap();
1169 assert_eq!(sub_chunk.non_null_blocks_count(), 0);
1170 sub_chunk.fill_block(DIRT.get_default_state()).unwrap();
1171 assert_eq!(sub_chunk.null_blocks_count(), 0);
1172
1173 unsafe {
1174
1175 sub_chunk.set_blocks_raw(vec![STONE.get_default_state()], (0..4096).map(|_| 0));
1176 assert_eq!(sub_chunk.null_blocks_count(), 0);
1177
1178 sub_chunk.set_blocks_raw(vec![AIR.get_default_state()], (0..4096).map(|_| 0));
1179 assert_eq!(sub_chunk.non_null_blocks_count(), 0);
1180
1181 }
1182
1183 }
1184
1185}