1use std::{
3 fmt::Debug,
4 io::Cursor,
5 ops::{Deref, DerefMut},
6 sync::{
7 Arc,
8 atomic::{AtomicU64, Ordering},
9 },
10};
11
12use parking_lot::{RwLockReadGuard, RwLockWriteGuard};
13use smallvec::SmallVec;
14use steel_registry::blocks::block_state_ext::BlockStateExt;
15use steel_registry::{REGISTRY, RegistryEntry};
16use steel_registry::{vanilla_biomes, vanilla_blocks};
17use steel_utils::{BlockPos, BlockStateId, ChunkPos, locks::SyncRwLock, serial::WriteTo};
18
19use crate::chunk::paletted_container::{BiomePalette, BlockPalette};
20use crate::physics::{
21 block_state_has_extensible_collision_behavior, block_state_may_expand_collision_cursor,
22};
23
24#[derive(Debug)]
31pub(crate) struct RandomTickSectionBits {
32 words: Box<[AtomicU64]>,
33 section_count: usize,
34}
35
36impl RandomTickSectionBits {
37 fn new(section_count: usize) -> Self {
38 let word_count = section_count.div_ceil(u64::BITS as usize);
39 let words = (0..word_count)
40 .map(|_| AtomicU64::new(0))
41 .collect::<Vec<_>>()
42 .into_boxed_slice();
43 Self {
44 words,
45 section_count,
46 }
47 }
48
49 fn set(&self, section_index: usize, randomly_ticking: bool) {
50 debug_assert!(section_index < self.section_count);
51 let word_index = section_index / u64::BITS as usize;
52 let mask = 1_u64 << (section_index % u64::BITS as usize);
53 if randomly_ticking {
54 self.words[word_index].fetch_or(mask, Ordering::Relaxed);
55 } else {
56 self.words[word_index].fetch_and(!mask, Ordering::Relaxed);
57 }
58 }
59
60 fn contains(&self, section_index: usize) -> bool {
61 debug_assert!(section_index < self.section_count);
62 let word_index = section_index / u64::BITS as usize;
63 let mask = 1_u64 << (section_index % u64::BITS as usize);
64 self.words[word_index].load(Ordering::Relaxed) & mask != 0
65 }
66
67 #[must_use]
72 pub(crate) fn next(&self, start: usize) -> Option<usize> {
73 if start >= self.section_count {
74 return None;
75 }
76
77 let mut word_index = start / u64::BITS as usize;
78 let bit_index = start % u64::BITS as usize;
79 let mut bits = self.words[word_index].load(Ordering::Relaxed) & (u64::MAX << bit_index);
80 loop {
81 if bits != 0 {
82 let section_index =
83 word_index * u64::BITS as usize + bits.trailing_zeros() as usize;
84 return (section_index < self.section_count).then_some(section_index);
85 }
86 word_index += 1;
87 let word = self.words.get(word_index)?;
88 bits = word.load(Ordering::Relaxed);
89 }
90 }
91
92 #[inline]
93 #[must_use]
94 pub(crate) fn is_empty(&self) -> bool {
95 self.next(0).is_none()
96 }
97}
98
99#[derive(Debug)]
101pub struct SectionHolder {
102 section: SyncRwLock<ChunkSection>,
104 randomly_ticking_sections: Arc<RandomTickSectionBits>,
106 section_index: usize,
107}
108
109impl SectionHolder {
110 #[must_use]
112 pub fn new(section: ChunkSection) -> Self {
113 let randomly_ticking_sections = Arc::new(RandomTickSectionBits::new(1));
114 Self::with_random_tick_index(section, randomly_ticking_sections, 0)
115 }
116
117 fn with_random_tick_index(
118 section: ChunkSection,
119 randomly_ticking_sections: Arc<RandomTickSectionBits>,
120 section_index: usize,
121 ) -> Self {
122 let randomly_ticking = section.is_randomly_ticking();
123 let result = Self {
124 section: SyncRwLock::new(section),
125 randomly_ticking_sections,
126 section_index,
127 };
128 if randomly_ticking {
129 result.randomly_ticking_sections.set(section_index, true);
130 }
131 result
132 }
133
134 #[inline]
140 #[must_use]
141 pub fn is_randomly_ticking(&self) -> bool {
142 self.randomly_ticking_sections.contains(self.section_index)
143 }
144
145 #[inline]
147 pub fn read(&self) -> RwLockReadGuard<'_, ChunkSection> {
148 self.section.read()
149 }
150
151 #[inline]
153 pub fn try_read(&self) -> Option<RwLockReadGuard<'_, ChunkSection>> {
154 self.section.try_read()
155 }
156
157 #[inline]
159 pub fn write(&self) -> SectionWriteGuard<'_> {
160 SectionWriteGuard::new(
161 self.section.write(),
162 &self.randomly_ticking_sections,
163 self.section_index,
164 )
165 }
166
167 #[inline]
169 pub fn try_write(&self) -> Option<SectionWriteGuard<'_>> {
170 self.section.try_write().map(|guard| {
171 SectionWriteGuard::new(guard, &self.randomly_ticking_sections, self.section_index)
172 })
173 }
174}
175
176pub struct SectionWriteGuard<'a> {
178 guard: RwLockWriteGuard<'a, ChunkSection>,
179 randomly_ticking_sections: &'a RandomTickSectionBits,
180 section_index: usize,
181 was_randomly_ticking: bool,
182}
183
184impl<'a> SectionWriteGuard<'a> {
185 fn new(
186 guard: RwLockWriteGuard<'a, ChunkSection>,
187 randomly_ticking_sections: &'a RandomTickSectionBits,
188 section_index: usize,
189 ) -> Self {
190 let was_randomly_ticking = guard.is_randomly_ticking();
191 Self {
192 guard,
193 randomly_ticking_sections,
194 section_index,
195 was_randomly_ticking,
196 }
197 }
198
199 pub fn set_block_state(
204 &mut self,
205 x: usize,
206 y: usize,
207 z: usize,
208 new_state: BlockStateId,
209 ) -> BlockStateId {
210 self.guard.set_block_state(x, y, z, new_state)
211 }
212
213 pub(crate) fn set_block_state_for_generation(
215 &mut self,
216 x: usize,
217 y: usize,
218 z: usize,
219 new_state: BlockStateId,
220 ) -> BlockStateId {
221 self.guard
222 .set_block_state_for_generation(x, y, z, new_state)
223 }
224
225 pub(crate) fn finalize_generation_counts_if_needed(&mut self) {
227 self.guard.finalize_generation_counts_if_needed();
228 }
229
230 pub fn recalculate_counts(&mut self) {
232 self.guard.recalculate_counts();
233 }
234}
235
236impl Deref for SectionWriteGuard<'_> {
237 type Target = ChunkSection;
238
239 fn deref(&self) -> &Self::Target {
240 &self.guard
241 }
242}
243
244impl DerefMut for SectionWriteGuard<'_> {
245 fn deref_mut(&mut self) -> &mut Self::Target {
246 self.guard.invalidate_stable_air_occupancy();
249 &mut self.guard
250 }
251}
252
253impl Drop for SectionWriteGuard<'_> {
254 fn drop(&mut self) {
255 let is_randomly_ticking = self.guard.is_randomly_ticking();
256 if is_randomly_ticking != self.was_randomly_ticking {
257 self.randomly_ticking_sections
258 .set(self.section_index, is_randomly_ticking);
259 }
260 }
261}
262
263#[derive(Debug)]
265pub struct Sections {
266 pub sections: Box<[SectionHolder]>,
268 randomly_ticking_sections: Arc<RandomTickSectionBits>,
269}
270
271#[derive(Clone, Copy, Debug, PartialEq, Eq)]
273pub(crate) struct BlockStateSectionCounts {
274 is_air: bool,
275 has_fluid: bool,
276 randomly_ticking_block: bool,
277 randomly_ticking_fluid: bool,
278}
279
280const BLOCKS_PER_SECTION: u16 = BlockPalette::VOLUME as u16;
281const STABLE_AIR_OCCUPANCY_ROW_COUNT: usize = BlockPalette::SIZE * BlockPalette::SIZE;
282
283#[derive(Debug)]
289enum StableAirOccupancy {
290 Unknown,
291 Empty,
292 Full,
293 Mixed {
294 rows: Box<[u16; STABLE_AIR_OCCUPANCY_ROW_COUNT]>,
295 occupied_count: u16,
296 },
297}
298
299impl StableAirOccupancy {
300 fn from_palette(states: &BlockPalette) -> Self {
301 let data = match states {
302 BlockPalette::Homogeneous(state) if is_stable_vanilla_air(*state) => {
303 return Self::Empty;
304 }
305 BlockPalette::Homogeneous(_) => return Self::Full,
306 BlockPalette::Heterogeneous(data) => data,
307 BlockPalette::Building(_) => return Self::Unknown,
308 };
309 let mut air_states = SmallVec::<[BlockStateId; 3]>::new();
311 let mut occupied_count = 0_u16;
312 for &(state, count) in data.palette() {
313 if is_stable_vanilla_air(state) {
314 air_states.push(state);
315 } else {
316 occupied_count += count;
317 }
318 }
319 if air_states.is_empty() {
320 return Self::Full;
321 }
322 if occupied_count == 0 {
323 return Self::Empty;
324 }
325
326 let mut rows = Box::new([0; STABLE_AIR_OCCUPANCY_ROW_COUNT]);
327 for (index, state) in data.iter_values().enumerate() {
329 if !air_states.contains(state) {
330 rows[index / BlockPalette::SIZE] |= 1_u16 << (index % BlockPalette::SIZE);
331 }
332 }
333 debug_assert!(occupied_count < BLOCKS_PER_SECTION);
334 Self::Mixed {
335 rows,
336 occupied_count,
337 }
338 }
339
340 fn record_change(
341 &mut self,
342 x: usize,
343 y: usize,
344 z: usize,
345 old_state: BlockStateId,
346 new_state: BlockStateId,
347 ) {
348 let old_occupied = !is_stable_vanilla_air(old_state);
349 let new_occupied = !is_stable_vanilla_air(new_state);
350 if old_occupied == new_occupied {
351 return;
352 }
353
354 let row_index = y * BlockPalette::SIZE + z;
355 let bit = 1_u16 << x;
356 match self {
357 Self::Unknown => {}
358 Self::Empty => {
359 debug_assert!(new_occupied);
360 let mut rows = Box::new([0; STABLE_AIR_OCCUPANCY_ROW_COUNT]);
361 rows[row_index] = bit;
362 *self = Self::Mixed {
363 rows,
364 occupied_count: 1,
365 };
366 }
367 Self::Full => {
368 debug_assert!(old_occupied);
369 let mut rows = Box::new([u16::MAX; STABLE_AIR_OCCUPANCY_ROW_COUNT]);
370 rows[row_index] &= !bit;
371 *self = Self::Mixed {
372 rows,
373 occupied_count: BLOCKS_PER_SECTION - 1,
374 };
375 }
376 Self::Mixed {
377 rows,
378 occupied_count,
379 } => {
380 if new_occupied {
381 rows[row_index] |= bit;
382 *occupied_count += 1;
383 if *occupied_count == BLOCKS_PER_SECTION {
384 *self = Self::Full;
385 }
386 } else {
387 rows[row_index] &= !bit;
388 *occupied_count -= 1;
389 if *occupied_count == 0 {
390 *self = Self::Empty;
391 }
392 }
393 }
394 }
395 }
396
397 fn box_is_clear(
398 &self,
399 min_x: usize,
400 max_x: usize,
401 min_y: usize,
402 max_y: usize,
403 min_z: usize,
404 max_z: usize,
405 ) -> bool {
406 debug_assert!(min_x <= max_x && max_x < BlockPalette::SIZE);
407 debug_assert!(min_y <= max_y && max_y < BlockPalette::SIZE);
408 debug_assert!(min_z <= max_z && max_z < BlockPalette::SIZE);
409
410 let Self::Mixed { rows, .. } = self else {
411 return matches!(self, Self::Empty);
412 };
413 let below_max = if max_x == u16::BITS as usize - 1 {
414 u16::MAX
415 } else {
416 (1_u16 << (max_x + 1)) - 1
417 };
418 let x_mask = below_max & (u16::MAX << min_x);
419 for y in min_y..=max_y {
420 for z in min_z..=max_z {
421 if rows[y * BlockPalette::SIZE + z] & x_mask != 0 {
422 return false;
423 }
424 }
425 }
426 true
427 }
428}
429
430fn is_stable_vanilla_air(state: BlockStateId) -> bool {
431 state.is_air()
433 && (state == vanilla_blocks::AIR.default_state()
434 || state == vanilla_blocks::CAVE_AIR.default_state()
435 || state == vanilla_blocks::VOID_AIR.default_state())
436}
437
438impl Sections {
439 #[must_use]
441 pub fn from_owned(sections: Box<[ChunkSection]>) -> Self {
442 let randomly_ticking_sections = Arc::new(RandomTickSectionBits::new(sections.len()));
443 let holders: Box<[SectionHolder]> = sections
444 .into_vec()
445 .into_iter()
446 .enumerate()
447 .map(|(section_index, section)| {
448 SectionHolder::with_random_tick_index(
449 section,
450 Arc::clone(&randomly_ticking_sections),
451 section_index,
452 )
453 })
454 .collect();
455 Self {
456 sections: holders,
457 randomly_ticking_sections,
458 }
459 }
460
461 #[must_use]
463 pub(crate) const fn random_tick_sections(&self) -> &Arc<RandomTickSectionBits> {
464 &self.randomly_ticking_sections
465 }
466
467 #[must_use]
469 pub fn get_relative_block(
470 &self,
471 relative_x: usize,
472 relative_y: usize,
473 relative_z: usize,
474 ) -> Option<BlockStateId> {
475 debug_assert!(relative_x < BlockPalette::SIZE);
476 debug_assert!(relative_z < BlockPalette::SIZE);
477
478 let section_index = relative_y / BlockPalette::SIZE;
479 let relative_y = relative_y % BlockPalette::SIZE;
480 self.sections.get(section_index).map(|section| {
481 section
482 .read()
483 .states
484 .get(relative_x, relative_y, relative_z)
485 })
486 }
487
488 pub fn read_column_into(&self, x: usize, z: usize, buf: &mut Vec<BlockStateId>) {
494 debug_assert!(x < BlockPalette::SIZE);
495 debug_assert!(z < BlockPalette::SIZE);
496
497 let total = self.sections.len() * 16;
498 if buf.len() != total {
499 buf.resize(total, BlockStateId::default());
500 }
501 for (i, holder) in self.sections.iter().enumerate() {
502 let guard = holder.read();
503 let base = i * 16;
504 guard
505 .states
506 .copy_column_into(x, z, &mut buf[base..base + 16]);
507 }
508 }
509
510 #[must_use]
515 pub fn read_all_biomes(&self) -> Box<[u16]> {
516 let total = self.sections.len() * 64;
517 let mut biomes = vec![0u16; total];
518 for (i, holder) in self.sections.iter().enumerate() {
519 let guard = holder.read();
520 let base = i * 64;
521 for qy in 0..4 {
522 for qz in 0..4 {
523 for qx in 0..4 {
524 biomes[base + qy * 16 + qz * 4 + qx] = guard.biomes.get(qx, qy, qz);
525 }
526 }
527 }
528 }
529 biomes.into_boxed_slice()
530 }
531
532 pub fn for_each_biome_id(&self, mut visitor: impl FnMut(u16)) {
535 for holder in &self.sections {
536 let guard = holder.read();
537 for qy in 0..4 {
538 for qz in 0..4 {
539 for qx in 0..4 {
540 visitor(guard.biomes.get(qx, qy, qz));
541 }
542 }
543 }
544 }
545 }
546
547 #[must_use]
549 pub fn section_emptiness_map(&self) -> Box<[bool]> {
550 self.sections
551 .iter()
552 .map(|section| section.read().is_empty())
553 .collect()
554 }
555
556 #[must_use]
558 pub fn block_light_sources(&self, chunk_pos: ChunkPos, min_y: i32) -> Vec<BlockPos> {
559 let mut sources = Vec::new();
560 let chunk_min_x = chunk_pos.0.x * BlockPalette::SIZE as i32;
561 let chunk_min_z = chunk_pos.0.y * BlockPalette::SIZE as i32;
562
563 for (section_index, section) in self.sections.iter().enumerate() {
564 let section_min_y = min_y + (section_index * BlockPalette::SIZE) as i32;
565 section.read().append_block_light_sources(
566 chunk_min_x,
567 section_min_y,
568 chunk_min_z,
569 &mut sources,
570 );
571 }
572
573 sources
574 }
575
576 pub fn write_column_blocks(&self, x: usize, z: usize, blocks: &[(usize, BlockStateId)]) {
580 const DIM: usize = BlockPalette::SIZE;
581 debug_assert!(x < DIM);
582 debug_assert!(z < DIM);
583
584 let mut i = 0;
585 while i < blocks.len() {
586 let section_idx = blocks[i].0 / DIM;
587 let mut guard = self.sections[section_idx].write();
588 guard.states.enter_building_mode();
589 let Some(cube) = guard.states.as_building_slice_mut() else {
590 unreachable!("just entered building mode")
591 };
592 let xz_base = z * DIM + x;
593 while i < blocks.len() && blocks[i].0 / DIM == section_idx {
594 let (rel_y, value) = blocks[i];
595 let local_y = rel_y % DIM;
596 cube[local_y * DIM * DIM + xz_base] = value;
597 i += 1;
598 }
599 }
600 }
601
602 pub fn write_block_batch(&self, blocks: &[(usize, usize, usize, BlockStateId)]) {
611 const DIM: usize = BlockPalette::SIZE;
612 let mut i = 0;
613 while i < blocks.len() {
614 let section_idx = blocks[i].1 / DIM;
615 let mut guard = self.sections[section_idx].write();
616 guard.states.enter_building_mode();
617 let Some(cube) = guard.states.as_building_slice_mut() else {
618 unreachable!("just entered building mode")
620 };
621 while i < blocks.len() && blocks[i].1 / DIM == section_idx {
622 let (x, rel_y, z, value) = blocks[i];
623 let local_y = rel_y % DIM;
624 cube[local_y * DIM * DIM + z * DIM + x] = value;
625 i += 1;
626 }
627 }
628 }
629
630 pub(crate) fn write_tracked_block_batch(&self, blocks: &[(usize, usize, usize, BlockStateId)]) {
635 const DIM: usize = BlockPalette::SIZE;
636 let mut i = 0;
637 while i < blocks.len() {
638 let section_idx = blocks[i].1 / DIM;
639 let mut guard = self.sections[section_idx].write();
640 while i < blocks.len() && blocks[i].1 / DIM == section_idx {
641 let (x, relative_y, z, value) = blocks[i];
642 guard.set_block_state(x, relative_y % DIM, z, value);
643 i += 1;
644 }
645 }
646 }
647
648 pub fn set_relative_block(
651 &self,
652 relative_x: usize,
653 relative_y: usize,
654 relative_z: usize,
655 value: BlockStateId,
656 ) {
657 debug_assert!(relative_x < BlockPalette::SIZE);
658 debug_assert!(relative_z < BlockPalette::SIZE);
659
660 let idx = relative_y / BlockPalette::SIZE;
661 let relative_y = relative_y % BlockPalette::SIZE;
662 let mut guard = self.sections[idx].write();
663 guard.set_block_state(relative_x, relative_y, relative_z, value);
664 }
665
666 pub(crate) fn set_relative_block_for_generation(
671 &self,
672 relative_x: usize,
673 relative_y: usize,
674 relative_z: usize,
675 value: BlockStateId,
676 ) {
677 debug_assert!(relative_x < BlockPalette::SIZE);
678 debug_assert!(relative_z < BlockPalette::SIZE);
679
680 let idx = relative_y / BlockPalette::SIZE;
681 let relative_y = relative_y % BlockPalette::SIZE;
682 let mut guard = self.sections[idx].write();
683 guard.set_block_state_for_generation(relative_x, relative_y, relative_z, value);
684 }
685}
686
687#[derive(Debug)]
692pub struct ChunkSection {
693 states: BlockPalette,
695 pub biomes: BiomePalette,
697 non_empty_block_count: u16,
700 fluid_count: u16,
703 pub ticking_block_count: u16,
705 ticking_fluid_count: u16,
707 stable_air_occupancy: StableAirOccupancy,
709}
710
711impl ChunkSection {
712 #[must_use]
717 pub const fn new_with_biomes(states: BlockPalette, biomes: BiomePalette) -> Self {
718 Self {
719 states,
720 biomes,
721 non_empty_block_count: 0,
722 fluid_count: 0,
723 ticking_block_count: 0,
724 ticking_fluid_count: 0,
725 stable_air_occupancy: StableAirOccupancy::Unknown,
726 }
727 }
728
729 #[must_use]
731 pub fn new_empty() -> Self {
732 let plains_id = vanilla_biomes::PLAINS.id() as u16;
733 Self {
734 states: BlockPalette::Homogeneous(BlockStateId(0)),
735 biomes: BiomePalette::Homogeneous(plains_id),
736 non_empty_block_count: 0,
737 fluid_count: 0,
738 ticking_block_count: 0,
739 ticking_fluid_count: 0,
740 stable_air_occupancy: StableAirOccupancy::Empty,
741 }
742 }
743
744 #[must_use]
746 pub(crate) const fn states(&self) -> &BlockPalette {
747 &self.states
748 }
749
750 #[must_use]
752 pub const fn is_empty(&self) -> bool {
753 self.non_empty_block_count == 0
754 }
755
756 #[must_use]
758 pub const fn is_randomly_ticking(&self) -> bool {
759 self.is_randomly_ticking_blocks() || self.is_randomly_ticking_fluids()
760 }
761
762 #[must_use]
764 pub const fn is_randomly_ticking_blocks(&self) -> bool {
765 self.ticking_block_count > 0
766 }
767
768 #[must_use]
770 pub const fn is_randomly_ticking_fluids(&self) -> bool {
771 self.ticking_fluid_count > 0
772 }
773
774 #[must_use]
776 pub fn maybe_has_block_light_sources(&self) -> bool {
777 !self.is_empty()
778 && self
779 .states
780 .maybe_has(|state| state.get_light_emission() > 0)
781 }
782
783 #[must_use]
789 pub(crate) fn maybe_has_special_colliding_blocks(&self) -> bool {
790 self.states
791 .maybe_has(block_state_may_expand_collision_cursor)
792 }
793
794 #[must_use]
800 pub(crate) fn maybe_has_extensible_collision_behavior(&self) -> bool {
801 self.states
802 .maybe_has(block_state_has_extensible_collision_behavior)
803 }
804
805 #[must_use]
807 pub(crate) fn stable_air_box_is_clear(
808 &self,
809 min_x: usize,
810 max_x: usize,
811 min_y: usize,
812 max_y: usize,
813 min_z: usize,
814 max_z: usize,
815 ) -> bool {
816 self.stable_air_occupancy
817 .box_is_clear(min_x, max_x, min_y, max_y, min_z, max_z)
818 }
819
820 fn invalidate_stable_air_occupancy(&mut self) {
821 self.stable_air_occupancy = StableAirOccupancy::Unknown;
822 }
823
824 pub fn append_block_light_sources(
826 &self,
827 chunk_min_x: i32,
828 section_min_y: i32,
829 chunk_min_z: i32,
830 sources: &mut Vec<BlockPos>,
831 ) {
832 if !self.maybe_has_block_light_sources() {
833 return;
834 }
835
836 for local_index in 0..BlockPalette::VOLUME {
837 let state = self.states.get_at_index(local_index);
838 if state.get_light_emission() == 0 {
839 continue;
840 }
841
842 sources.push(BlockPos::new(
843 chunk_min_x + (local_index & 15) as i32,
844 section_min_y + (local_index >> 8) as i32,
845 chunk_min_z + ((local_index >> 4) & 15) as i32,
846 ));
847 }
848 }
849
850 #[must_use]
852 pub const fn non_empty_block_count(&self) -> u16 {
853 self.non_empty_block_count
854 }
855
856 #[must_use]
858 pub const fn fluid_count(&self) -> u16 {
859 self.fluid_count
860 }
861
862 #[must_use]
864 pub const fn has_fluid(&self) -> bool {
865 self.fluid_count > 0
866 }
867
868 #[must_use]
870 pub const fn ticking_block_count(&self) -> u16 {
871 self.ticking_block_count
872 }
873
874 #[must_use]
876 pub const fn ticking_fluid_count(&self) -> u16 {
877 self.ticking_fluid_count
878 }
879
880 pub fn recalculate_counts(&mut self) {
889 self.recalculate_counts_from_palette(Self::block_state_section_counts);
890 }
891
892 fn recalculate_counts_from_palette(
893 &mut self,
894 mut counts_for_state: impl FnMut(BlockStateId) -> BlockStateSectionCounts,
895 ) {
896 self.states.finalize_building();
897 let stable_air_occupancy = StableAirOccupancy::from_palette(&self.states);
898
899 let mut non_empty: u16 = 0;
900 let mut fluid: u16 = 0;
901 let mut ticking_blocks: u16 = 0;
902 let mut ticking_fluids: u16 = 0;
903
904 match &self.states {
905 BlockPalette::Homogeneous(state) => {
906 let counts = counts_for_state(*state);
907 Self::accumulate_counter_traits(
908 &mut non_empty,
909 &mut fluid,
910 &mut ticking_blocks,
911 &mut ticking_fluids,
912 counts,
913 BLOCKS_PER_SECTION,
914 );
915 }
916 BlockPalette::Heterogeneous(data) => {
917 for &(state, count) in data.palette() {
918 let counts = counts_for_state(state);
919 Self::accumulate_counter_traits(
920 &mut non_empty,
921 &mut fluid,
922 &mut ticking_blocks,
923 &mut ticking_fluids,
924 counts,
925 count,
926 );
927 }
928 }
929 BlockPalette::Building(_) => unreachable!("finalize_building was just called"),
930 }
931
932 self.non_empty_block_count = non_empty;
933 self.fluid_count = fluid;
934 self.ticking_block_count = ticking_blocks;
935 self.ticking_fluid_count = ticking_fluids;
936 self.stable_air_occupancy = stable_air_occupancy;
937 }
938
939 const fn accumulate_counter_traits(
940 non_empty: &mut u16,
941 fluid: &mut u16,
942 ticking_blocks: &mut u16,
943 ticking_fluids: &mut u16,
944 counts: BlockStateSectionCounts,
945 block_count: u16,
946 ) {
947 if !counts.is_air {
948 *non_empty += block_count;
949 }
950 if counts.has_fluid {
951 *fluid += block_count;
952 }
953 if counts.randomly_ticking_block {
954 *ticking_blocks += block_count;
955 }
956 if counts.randomly_ticking_fluid {
957 *ticking_fluids += block_count;
958 }
959 }
960
961 #[must_use]
968 pub fn contains_poi(&self) -> bool {
969 let poi = ®ISTRY.poi_types;
970 match &self.states {
971 BlockPalette::Homogeneous(state) => poi.is_poi_state(*state),
972 BlockPalette::Heterogeneous(data) => data
973 .palette()
974 .iter()
975 .any(|(state, _)| poi.is_poi_state(*state)),
976 BlockPalette::Building(_) => true,
979 }
980 }
981
982 pub fn set_block_state(
987 &mut self,
988 x: usize,
989 y: usize,
990 z: usize,
991 new_state: BlockStateId,
992 ) -> BlockStateId {
993 self.ensure_counter_ready_for_delta();
994 let old_state = self.states.set(x, y, z, new_state);
995
996 if old_state != new_state {
997 let old_counts = Self::block_state_section_counts(old_state);
998 let new_counts = Self::block_state_section_counts(new_state);
999 self.apply_count_change(old_counts, new_counts);
1000 self.stable_air_occupancy
1001 .record_change(x, y, z, old_state, new_state);
1002 }
1003
1004 old_state
1005 }
1006
1007 pub(crate) fn set_block_state_for_generation(
1013 &mut self,
1014 x: usize,
1015 y: usize,
1016 z: usize,
1017 new_state: BlockStateId,
1018 ) -> BlockStateId {
1019 self.invalidate_stable_air_occupancy();
1020 self.states.enter_building_mode();
1021 self.states.set(x, y, z, new_state)
1022 }
1023
1024 pub(crate) fn block_state_section_counts(state: BlockStateId) -> BlockStateSectionCounts {
1026 let metadata = state.get_ticking_metadata();
1027 BlockStateSectionCounts {
1028 is_air: metadata.is_air(),
1029 has_fluid: metadata.has_fluid(),
1030 randomly_ticking_block: metadata.randomly_ticking_block(),
1031 randomly_ticking_fluid: metadata.randomly_ticking_fluid(),
1032 }
1033 }
1034
1035 pub(crate) fn finalize_generation_counts_if_needed(&mut self) {
1036 if matches!(&self.states, BlockPalette::Building(_)) {
1037 self.recalculate_counts();
1038 }
1039 }
1040
1041 fn ensure_counter_ready_for_delta(&mut self) {
1042 if matches!(&self.states, BlockPalette::Building(_)) {
1043 log::debug!(
1044 "finalizing worldgen Building palette before applying a counter-aware \
1045 block-state delta"
1046 );
1047 self.recalculate_counts();
1048 }
1049 }
1050
1051 const fn apply_count_change(
1052 &mut self,
1053 old_counts: BlockStateSectionCounts,
1054 new_counts: BlockStateSectionCounts,
1055 ) {
1056 if !old_counts.is_air && new_counts.is_air {
1057 self.non_empty_block_count -= 1;
1058 } else if old_counts.is_air && !new_counts.is_air {
1059 self.non_empty_block_count += 1;
1060 }
1061
1062 if old_counts.has_fluid && !new_counts.has_fluid {
1063 self.fluid_count -= 1;
1064 } else if !old_counts.has_fluid && new_counts.has_fluid {
1065 self.fluid_count += 1;
1066 }
1067
1068 if old_counts.randomly_ticking_block && !new_counts.randomly_ticking_block {
1069 self.ticking_block_count -= 1;
1070 } else if !old_counts.randomly_ticking_block && new_counts.randomly_ticking_block {
1071 self.ticking_block_count += 1;
1072 }
1073
1074 if old_counts.randomly_ticking_fluid && !new_counts.randomly_ticking_fluid {
1075 self.ticking_fluid_count -= 1;
1076 } else if !old_counts.randomly_ticking_fluid && new_counts.randomly_ticking_fluid {
1077 self.ticking_fluid_count += 1;
1078 }
1079 }
1080
1081 pub fn write(&self, writer: &mut Cursor<Vec<u8>>) {
1086 self.non_empty_block_count
1087 .write(writer)
1088 .expect("Failed to write block count");
1089 self.fluid_count
1090 .write(writer)
1091 .expect("Failed to write fluid count");
1092
1093 self.states
1094 .write(writer)
1095 .expect("Failed to write block states");
1096 self.biomes.write(writer).expect("Failed to write biomes");
1097 }
1098}
1099
1100#[cfg(test)]
1101mod tests {
1102 use steel_registry::init_vanilla_registry;
1103 use steel_registry::vanilla_blocks;
1104
1105 use crate::behavior::init_behaviors;
1106
1107 use super::*;
1108
1109 const SECTION_MAX_LOCAL_COORDINATE: usize = BlockPalette::SIZE - 1;
1110
1111 fn plains_biomes() -> BiomePalette {
1112 BiomePalette::Homogeneous(vanilla_biomes::PLAINS.id() as u16)
1113 }
1114
1115 fn init_test_behaviors() {
1116 init_vanilla_registry();
1117 init_behaviors();
1118 }
1119
1120 fn entire_section_is_stable_air(section: &ChunkSection) -> bool {
1121 section.stable_air_box_is_clear(
1122 0,
1123 SECTION_MAX_LOCAL_COORDINATE,
1124 0,
1125 SECTION_MAX_LOCAL_COORDINATE,
1126 0,
1127 SECTION_MAX_LOCAL_COORDINATE,
1128 )
1129 }
1130
1131 #[test]
1132 fn stable_air_occupancy_tracks_canonical_air_and_non_air_transitions() {
1133 init_test_behaviors();
1134 let mut section = ChunkSection::new_empty();
1135
1136 assert!(entire_section_is_stable_air(§ion));
1137 section.set_block_state(2, 3, 4, vanilla_blocks::CAVE_AIR.default_state());
1138 section.set_block_state(5, 6, 7, vanilla_blocks::VOID_AIR.default_state());
1139 assert!(entire_section_is_stable_air(§ion));
1140
1141 section.set_block_state(2, 3, 4, vanilla_blocks::STONE.default_state());
1142 assert!(!section.stable_air_box_is_clear(2, 2, 3, 3, 4, 4));
1143 assert!(section.stable_air_box_is_clear(3, 3, 3, 3, 4, 4));
1144 section.set_block_state(2, 3, 4, vanilla_blocks::AIR.default_state());
1145 assert!(entire_section_is_stable_air(§ion));
1146
1147 let mut full = ChunkSection::new_with_biomes(
1148 BlockPalette::Homogeneous(vanilla_blocks::STONE.default_state()),
1149 plains_biomes(),
1150 );
1151 full.recalculate_counts();
1152 assert!(!full.stable_air_box_is_clear(0, 0, 0, 0, 0, 0));
1153 full.set_block_state(0, 0, 0, vanilla_blocks::AIR.default_state());
1154 assert!(full.stable_air_box_is_clear(0, 0, 0, 0, 0, 0));
1155 assert!(!full.stable_air_box_is_clear(1, 1, 0, 0, 0, 0));
1156 full.set_block_state(0, 0, 0, vanilla_blocks::STONE.default_state());
1157 assert!(!full.stable_air_box_is_clear(0, 0, 0, 0, 0, 0));
1158 }
1159
1160 #[test]
1161 fn arbitrary_palette_mutation_invalidates_stable_air_certificate_until_recount() {
1162 init_test_behaviors();
1163 let holder = SectionHolder::new(ChunkSection::new_empty());
1164 {
1165 let mut guard = holder.write();
1166 guard.states.enter_building_mode();
1167 }
1168 assert!(!entire_section_is_stable_air(&holder.read()));
1169
1170 holder.write().recalculate_counts();
1171 assert!(entire_section_is_stable_air(&holder.read()));
1172 }
1173
1174 #[test]
1175 fn write_guard_setter_keeps_stable_air_occupancy_tracked() {
1176 const LOCAL_X: usize = 2;
1177 const LOCAL_Y: usize = 3;
1178 const LOCAL_Z: usize = 4;
1179
1180 init_test_behaviors();
1181 let holder = SectionHolder::new(ChunkSection::new_empty());
1182
1183 holder.write().set_block_state(
1184 LOCAL_X,
1185 LOCAL_Y,
1186 LOCAL_Z,
1187 vanilla_blocks::STONE.default_state(),
1188 );
1189 assert!(
1190 !holder
1191 .read()
1192 .stable_air_box_is_clear(LOCAL_X, LOCAL_X, LOCAL_Y, LOCAL_Y, LOCAL_Z, LOCAL_Z,)
1193 );
1194
1195 holder.write().set_block_state(
1196 LOCAL_X,
1197 LOCAL_Y,
1198 LOCAL_Z,
1199 vanilla_blocks::AIR.default_state(),
1200 );
1201 assert!(entire_section_is_stable_air(&holder.read()));
1202 }
1203
1204 #[test]
1205 fn raw_bulk_writes_invalidate_stable_air_certificate_until_recount() {
1206 init_test_behaviors();
1207 let sections = Sections::from_owned(vec![ChunkSection::new_empty()].into_boxed_slice());
1208 let holder = §ions.sections[0];
1209
1210 sections.write_block_batch(&[(1, 2, 3, vanilla_blocks::STONE.default_state())]);
1211 assert!(!entire_section_is_stable_air(&holder.read()));
1212 holder.write().recalculate_counts();
1213 assert!(!holder.read().stable_air_box_is_clear(1, 1, 2, 2, 3, 3));
1214
1215 sections.write_column_blocks(1, 3, &[(2, vanilla_blocks::AIR.default_state())]);
1216 assert!(!entire_section_is_stable_air(&holder.read()));
1217 holder.write().recalculate_counts();
1218 assert!(entire_section_is_stable_air(&holder.read()));
1219 }
1220
1221 #[test]
1222 fn recount_uses_homogeneous_palette_frequency() {
1223 init_test_behaviors();
1224
1225 let mut section = ChunkSection::new_with_biomes(
1226 BlockPalette::Homogeneous(vanilla_blocks::LAVA.default_state()),
1227 plains_biomes(),
1228 );
1229
1230 section.recalculate_counts();
1231
1232 assert_eq!(section.non_empty_block_count(), BLOCKS_PER_SECTION);
1233 assert_eq!(section.fluid_count(), BLOCKS_PER_SECTION);
1234 assert_eq!(section.ticking_block_count(), BLOCKS_PER_SECTION);
1235 assert_eq!(section.ticking_fluid_count(), BLOCKS_PER_SECTION);
1236 }
1237
1238 #[test]
1239 fn recount_uses_heterogeneous_palette_frequencies() {
1240 init_test_behaviors();
1241
1242 let air = vanilla_blocks::AIR.default_state();
1243 let stone = vanilla_blocks::STONE.default_state();
1244 let water = vanilla_blocks::WATER.default_state();
1245 let lava = vanilla_blocks::LAVA.default_state();
1246 let mut cube = Box::new([[[air; 16]; 16]; 16]);
1247
1248 cube[0][0][0] = stone;
1249 cube[1][0][0] = stone;
1250 cube[2][0][0] = water;
1251 cube[3][0][0] = water;
1252 cube[4][0][0] = water;
1253 cube[5][0][0] = lava;
1254
1255 let mut section =
1256 ChunkSection::new_with_biomes(BlockPalette::from_cube(cube), plains_biomes());
1257
1258 section.recalculate_counts();
1259
1260 assert_eq!(section.non_empty_block_count(), 6);
1261 assert_eq!(section.fluid_count(), 4);
1262 assert_eq!(section.ticking_block_count(), 1);
1263 assert_eq!(section.ticking_fluid_count(), 1);
1264 }
1265
1266 #[test]
1267 fn holder_keeps_random_tick_eligibility_in_sync() {
1268 init_test_behaviors();
1269
1270 let mut loaded_section = ChunkSection::new_with_biomes(
1271 BlockPalette::Homogeneous(vanilla_blocks::LAVA.default_state()),
1272 plains_biomes(),
1273 );
1274 loaded_section.recalculate_counts();
1275 let loaded_holder = SectionHolder::new(loaded_section);
1276 assert!(loaded_holder.is_randomly_ticking());
1277
1278 let holder = SectionHolder::new(ChunkSection::new_empty());
1279 {
1280 let mut section = holder.write();
1281 section.set_block_state(0, 0, 0, vanilla_blocks::LAVA.default_state());
1282 assert_eq!(section.ticking_block_count(), 1);
1283 assert_eq!(section.ticking_fluid_count(), 1);
1284 }
1285 assert!(holder.is_randomly_ticking());
1286
1287 {
1288 let Some(mut section) = holder.try_write() else {
1289 panic!("uncontended section write lock was unavailable");
1290 };
1291 section.set_block_state(0, 0, 0, vanilla_blocks::AIR.default_state());
1292 assert_eq!(section.ticking_block_count(), 0);
1293 assert_eq!(section.ticking_fluid_count(), 0);
1294 }
1295 assert!(!holder.is_randomly_ticking());
1296 }
1297
1298 #[test]
1299 fn shared_random_tick_section_bits_follow_cross_word_updates() {
1300 init_test_behaviors();
1301 let sections = Sections::from_owned(
1302 (0..65)
1303 .map(|_| ChunkSection::new_empty())
1304 .collect::<Vec<_>>()
1305 .into_boxed_slice(),
1306 );
1307 let bits = Arc::clone(sections.random_tick_sections());
1308 assert!(bits.is_empty());
1309
1310 {
1311 let mut section = sections.sections[64].write();
1312 section.set_block_state(0, 0, 0, vanilla_blocks::LAVA.default_state());
1313 }
1314 assert_eq!(bits.next(0), Some(64));
1315
1316 {
1317 let mut section = sections.sections[1].write();
1318 section.set_block_state(0, 0, 0, vanilla_blocks::LAVA.default_state());
1319 }
1320 assert_eq!(bits.next(0), Some(1));
1321 assert_eq!(bits.next(2), Some(64));
1322
1323 {
1324 let mut section = sections.sections[1].write();
1325 section.set_block_state(0, 0, 0, vanilla_blocks::AIR.default_state());
1326 }
1327 assert_eq!(bits.next(0), Some(64));
1328
1329 {
1330 let mut section = sections.sections[64].write();
1331 section.set_block_state(0, 0, 0, vanilla_blocks::AIR.default_state());
1332 }
1333 assert!(bits.is_empty());
1334 }
1335
1336 #[test]
1337 fn generation_recount_publishes_random_tick_section_bit() {
1338 init_test_behaviors();
1339 let sections = Sections::from_owned(vec![ChunkSection::new_empty()].into_boxed_slice());
1340 let bits = Arc::clone(sections.random_tick_sections());
1341
1342 {
1343 let mut section = sections.sections[0].write();
1344 section.set_block_state_for_generation(0, 0, 0, vanilla_blocks::LAVA.default_state());
1345 }
1346 assert!(bits.is_empty());
1347
1348 {
1349 let mut section = sections.sections[0].write();
1350 section.finalize_generation_counts_if_needed();
1351 }
1352 assert_eq!(bits.next(0), Some(0));
1353 }
1354
1355 #[test]
1356 fn counter_aware_write_recounts_building_palette_before_delta() {
1357 init_test_behaviors();
1358
1359 let air = vanilla_blocks::AIR.default_state();
1360 let stone = vanilla_blocks::STONE.default_state();
1361 let mut section = ChunkSection::new_empty();
1362
1363 section.set_block_state_for_generation(0, 0, 0, stone);
1364 assert_eq!(section.non_empty_block_count(), 0);
1365
1366 let old_state = section.set_block_state(0, 0, 0, air);
1367
1368 assert_eq!(old_state, stone);
1369 assert_eq!(section.non_empty_block_count(), 0);
1370
1371 let old_state = section.set_block_state(0, 0, 0, stone);
1372
1373 assert_eq!(old_state, air);
1374 assert_eq!(section.non_empty_block_count(), 1);
1375 }
1376}