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 steel_registry::blocks::block_state_ext::BlockStateExt;
14use steel_registry::vanilla_biomes;
15use steel_registry::{REGISTRY, RegistryEntry};
16use steel_utils::{BlockPos, BlockStateId, ChunkPos, locks::SyncRwLock, serial::WriteTo};
17
18use crate::chunk::paletted_container::{BiomePalette, BlockPalette};
19
20#[derive(Debug)]
27pub(crate) struct RandomTickSectionBits {
28 words: Box<[AtomicU64]>,
29 section_count: usize,
30}
31
32impl RandomTickSectionBits {
33 fn new(section_count: usize) -> Self {
34 let word_count = section_count.div_ceil(u64::BITS as usize);
35 let words = (0..word_count)
36 .map(|_| AtomicU64::new(0))
37 .collect::<Vec<_>>()
38 .into_boxed_slice();
39 Self {
40 words,
41 section_count,
42 }
43 }
44
45 fn set(&self, section_index: usize, randomly_ticking: bool) {
46 debug_assert!(section_index < self.section_count);
47 let word_index = section_index / u64::BITS as usize;
48 let mask = 1_u64 << (section_index % u64::BITS as usize);
49 if randomly_ticking {
50 self.words[word_index].fetch_or(mask, Ordering::Relaxed);
51 } else {
52 self.words[word_index].fetch_and(!mask, Ordering::Relaxed);
53 }
54 }
55
56 fn contains(&self, section_index: usize) -> bool {
57 debug_assert!(section_index < self.section_count);
58 let word_index = section_index / u64::BITS as usize;
59 let mask = 1_u64 << (section_index % u64::BITS as usize);
60 self.words[word_index].load(Ordering::Relaxed) & mask != 0
61 }
62
63 #[must_use]
68 pub(crate) fn next(&self, start: usize) -> Option<usize> {
69 if start >= self.section_count {
70 return None;
71 }
72
73 let mut word_index = start / u64::BITS as usize;
74 let bit_index = start % u64::BITS as usize;
75 let mut bits = self.words[word_index].load(Ordering::Relaxed) & (u64::MAX << bit_index);
76 loop {
77 if bits != 0 {
78 let section_index =
79 word_index * u64::BITS as usize + bits.trailing_zeros() as usize;
80 return (section_index < self.section_count).then_some(section_index);
81 }
82 word_index += 1;
83 let word = self.words.get(word_index)?;
84 bits = word.load(Ordering::Relaxed);
85 }
86 }
87
88 #[inline]
89 #[must_use]
90 pub(crate) fn is_empty(&self) -> bool {
91 self.next(0).is_none()
92 }
93}
94
95#[derive(Debug)]
97pub struct SectionHolder {
98 section: SyncRwLock<ChunkSection>,
100 randomly_ticking_sections: Arc<RandomTickSectionBits>,
102 section_index: usize,
103}
104
105impl SectionHolder {
106 #[must_use]
108 pub fn new(section: ChunkSection) -> Self {
109 let randomly_ticking_sections = Arc::new(RandomTickSectionBits::new(1));
110 Self::with_random_tick_index(section, randomly_ticking_sections, 0)
111 }
112
113 fn with_random_tick_index(
114 section: ChunkSection,
115 randomly_ticking_sections: Arc<RandomTickSectionBits>,
116 section_index: usize,
117 ) -> Self {
118 let randomly_ticking = section.is_randomly_ticking();
119 let result = Self {
120 section: SyncRwLock::new(section),
121 randomly_ticking_sections,
122 section_index,
123 };
124 if randomly_ticking {
125 result.randomly_ticking_sections.set(section_index, true);
126 }
127 result
128 }
129
130 #[inline]
136 #[must_use]
137 pub fn is_randomly_ticking(&self) -> bool {
138 self.randomly_ticking_sections.contains(self.section_index)
139 }
140
141 #[inline]
143 pub fn read(&self) -> RwLockReadGuard<'_, ChunkSection> {
144 self.section.read()
145 }
146
147 #[inline]
149 pub fn try_read(&self) -> Option<RwLockReadGuard<'_, ChunkSection>> {
150 self.section.try_read()
151 }
152
153 #[inline]
155 pub fn write(&self) -> SectionWriteGuard<'_> {
156 SectionWriteGuard::new(
157 self.section.write(),
158 &self.randomly_ticking_sections,
159 self.section_index,
160 )
161 }
162
163 #[inline]
165 pub fn try_write(&self) -> Option<SectionWriteGuard<'_>> {
166 self.section.try_write().map(|guard| {
167 SectionWriteGuard::new(guard, &self.randomly_ticking_sections, self.section_index)
168 })
169 }
170}
171
172pub struct SectionWriteGuard<'a> {
174 guard: RwLockWriteGuard<'a, ChunkSection>,
175 randomly_ticking_sections: &'a RandomTickSectionBits,
176 section_index: usize,
177 was_randomly_ticking: bool,
178}
179
180impl<'a> SectionWriteGuard<'a> {
181 fn new(
182 guard: RwLockWriteGuard<'a, ChunkSection>,
183 randomly_ticking_sections: &'a RandomTickSectionBits,
184 section_index: usize,
185 ) -> Self {
186 let was_randomly_ticking = guard.is_randomly_ticking();
187 Self {
188 guard,
189 randomly_ticking_sections,
190 section_index,
191 was_randomly_ticking,
192 }
193 }
194}
195
196impl Deref for SectionWriteGuard<'_> {
197 type Target = ChunkSection;
198
199 fn deref(&self) -> &Self::Target {
200 &self.guard
201 }
202}
203
204impl DerefMut for SectionWriteGuard<'_> {
205 fn deref_mut(&mut self) -> &mut Self::Target {
206 &mut self.guard
207 }
208}
209
210impl Drop for SectionWriteGuard<'_> {
211 fn drop(&mut self) {
212 let is_randomly_ticking = self.guard.is_randomly_ticking();
213 if is_randomly_ticking != self.was_randomly_ticking {
214 self.randomly_ticking_sections
215 .set(self.section_index, is_randomly_ticking);
216 }
217 }
218}
219
220#[derive(Debug)]
222pub struct Sections {
223 pub sections: Box<[SectionHolder]>,
225 randomly_ticking_sections: Arc<RandomTickSectionBits>,
226}
227
228#[derive(Clone, Copy, Debug, PartialEq, Eq)]
230pub(crate) struct BlockStateSectionCounts {
231 is_air: bool,
232 has_fluid: bool,
233 randomly_ticking_block: bool,
234 randomly_ticking_fluid: bool,
235}
236
237const BLOCKS_PER_SECTION: u16 = 16 * 16 * 16;
238
239impl Sections {
240 #[must_use]
242 pub fn from_owned(sections: Box<[ChunkSection]>) -> Self {
243 let randomly_ticking_sections = Arc::new(RandomTickSectionBits::new(sections.len()));
244 let holders: Box<[SectionHolder]> = sections
245 .into_vec()
246 .into_iter()
247 .enumerate()
248 .map(|(section_index, section)| {
249 SectionHolder::with_random_tick_index(
250 section,
251 Arc::clone(&randomly_ticking_sections),
252 section_index,
253 )
254 })
255 .collect();
256 Self {
257 sections: holders,
258 randomly_ticking_sections,
259 }
260 }
261
262 #[must_use]
264 pub(crate) const fn random_tick_sections(&self) -> &Arc<RandomTickSectionBits> {
265 &self.randomly_ticking_sections
266 }
267
268 #[must_use]
270 pub fn get_relative_block(
271 &self,
272 relative_x: usize,
273 relative_y: usize,
274 relative_z: usize,
275 ) -> Option<BlockStateId> {
276 debug_assert!(relative_x < BlockPalette::SIZE);
277 debug_assert!(relative_z < BlockPalette::SIZE);
278
279 let section_index = relative_y / BlockPalette::SIZE;
280 let relative_y = relative_y % BlockPalette::SIZE;
281 self.sections.get(section_index).map(|section| {
282 section
283 .read()
284 .states
285 .get(relative_x, relative_y, relative_z)
286 })
287 }
288
289 pub fn read_column_into(&self, x: usize, z: usize, buf: &mut Vec<BlockStateId>) {
295 debug_assert!(x < BlockPalette::SIZE);
296 debug_assert!(z < BlockPalette::SIZE);
297
298 let total = self.sections.len() * 16;
299 if buf.len() != total {
300 buf.resize(total, BlockStateId::default());
301 }
302 for (i, holder) in self.sections.iter().enumerate() {
303 let guard = holder.read();
304 let base = i * 16;
305 guard
306 .states
307 .copy_column_into(x, z, &mut buf[base..base + 16]);
308 }
309 }
310
311 #[must_use]
316 pub fn read_all_biomes(&self) -> Box<[u16]> {
317 let total = self.sections.len() * 64;
318 let mut biomes = vec![0u16; total];
319 for (i, holder) in self.sections.iter().enumerate() {
320 let guard = holder.read();
321 let base = i * 64;
322 for qy in 0..4 {
323 for qz in 0..4 {
324 for qx in 0..4 {
325 biomes[base + qy * 16 + qz * 4 + qx] = guard.biomes.get(qx, qy, qz);
326 }
327 }
328 }
329 }
330 biomes.into_boxed_slice()
331 }
332
333 pub fn for_each_biome_id(&self, mut visitor: impl FnMut(u16)) {
336 for holder in &self.sections {
337 let guard = holder.read();
338 for qy in 0..4 {
339 for qz in 0..4 {
340 for qx in 0..4 {
341 visitor(guard.biomes.get(qx, qy, qz));
342 }
343 }
344 }
345 }
346 }
347
348 #[must_use]
350 pub fn section_emptiness_map(&self) -> Box<[bool]> {
351 self.sections
352 .iter()
353 .map(|section| section.read().is_empty())
354 .collect()
355 }
356
357 #[must_use]
359 pub fn block_light_sources(&self, chunk_pos: ChunkPos, min_y: i32) -> Vec<BlockPos> {
360 let mut sources = Vec::new();
361 let chunk_min_x = chunk_pos.0.x * BlockPalette::SIZE as i32;
362 let chunk_min_z = chunk_pos.0.y * BlockPalette::SIZE as i32;
363
364 for (section_index, section) in self.sections.iter().enumerate() {
365 let section_min_y = min_y + (section_index * BlockPalette::SIZE) as i32;
366 section.read().append_block_light_sources(
367 chunk_min_x,
368 section_min_y,
369 chunk_min_z,
370 &mut sources,
371 );
372 }
373
374 sources
375 }
376
377 pub fn write_column_blocks(&self, x: usize, z: usize, blocks: &[(usize, BlockStateId)]) {
381 const DIM: usize = BlockPalette::SIZE;
382 debug_assert!(x < DIM);
383 debug_assert!(z < DIM);
384
385 let mut i = 0;
386 while i < blocks.len() {
387 let section_idx = blocks[i].0 / DIM;
388 let mut guard = self.sections[section_idx].write();
389 guard.states.enter_building_mode();
390 let Some(cube) = guard.states.as_building_slice_mut() else {
391 unreachable!("just entered building mode")
392 };
393 let xz_base = z * DIM + x;
394 while i < blocks.len() && blocks[i].0 / DIM == section_idx {
395 let (rel_y, value) = blocks[i];
396 let local_y = rel_y % DIM;
397 cube[local_y * DIM * DIM + xz_base] = value;
398 i += 1;
399 }
400 }
401 }
402
403 pub fn write_block_batch(&self, blocks: &[(usize, usize, usize, BlockStateId)]) {
412 const DIM: usize = BlockPalette::SIZE;
413 let mut i = 0;
414 while i < blocks.len() {
415 let section_idx = blocks[i].1 / DIM;
416 let mut guard = self.sections[section_idx].write();
417 guard.states.enter_building_mode();
418 let Some(cube) = guard.states.as_building_slice_mut() else {
419 unreachable!("just entered building mode")
421 };
422 while i < blocks.len() && blocks[i].1 / DIM == section_idx {
423 let (x, rel_y, z, value) = blocks[i];
424 let local_y = rel_y % DIM;
425 cube[local_y * DIM * DIM + z * DIM + x] = value;
426 i += 1;
427 }
428 }
429 }
430
431 pub(crate) fn write_tracked_block_batch(&self, blocks: &[(usize, usize, usize, BlockStateId)]) {
436 const DIM: usize = BlockPalette::SIZE;
437 let mut i = 0;
438 while i < blocks.len() {
439 let section_idx = blocks[i].1 / DIM;
440 let mut guard = self.sections[section_idx].write();
441 while i < blocks.len() && blocks[i].1 / DIM == section_idx {
442 let (x, relative_y, z, value) = blocks[i];
443 guard.set_block_state(x, relative_y % DIM, z, value);
444 i += 1;
445 }
446 }
447 }
448
449 pub fn set_relative_block(
452 &self,
453 relative_x: usize,
454 relative_y: usize,
455 relative_z: usize,
456 value: BlockStateId,
457 ) {
458 debug_assert!(relative_x < BlockPalette::SIZE);
459 debug_assert!(relative_z < BlockPalette::SIZE);
460
461 let idx = relative_y / BlockPalette::SIZE;
462 let relative_y = relative_y % BlockPalette::SIZE;
463 let mut guard = self.sections[idx].write();
464 guard.set_block_state(relative_x, relative_y, relative_z, value);
465 }
466
467 pub(crate) fn set_relative_block_for_generation(
472 &self,
473 relative_x: usize,
474 relative_y: usize,
475 relative_z: usize,
476 value: BlockStateId,
477 ) {
478 debug_assert!(relative_x < BlockPalette::SIZE);
479 debug_assert!(relative_z < BlockPalette::SIZE);
480
481 let idx = relative_y / BlockPalette::SIZE;
482 let relative_y = relative_y % BlockPalette::SIZE;
483 let mut guard = self.sections[idx].write();
484 guard.set_block_state_for_generation(relative_x, relative_y, relative_z, value);
485 }
486}
487
488#[derive(Debug)]
493pub struct ChunkSection {
494 pub states: BlockPalette,
496 pub biomes: BiomePalette,
498 non_empty_block_count: u16,
501 fluid_count: u16,
504 pub ticking_block_count: u16,
506 ticking_fluid_count: u16,
508}
509
510impl ChunkSection {
511 #[must_use]
516 pub const fn new_with_biomes(states: BlockPalette, biomes: BiomePalette) -> Self {
517 Self {
518 states,
519 biomes,
520 non_empty_block_count: 0,
521 fluid_count: 0,
522 ticking_block_count: 0,
523 ticking_fluid_count: 0,
524 }
525 }
526
527 #[must_use]
529 pub fn new_empty() -> Self {
530 let plains_id = vanilla_biomes::PLAINS.id() as u16;
531 Self {
532 states: BlockPalette::Homogeneous(BlockStateId(0)),
533 biomes: BiomePalette::Homogeneous(plains_id),
534 non_empty_block_count: 0,
535 fluid_count: 0,
536 ticking_block_count: 0,
537 ticking_fluid_count: 0,
538 }
539 }
540
541 #[must_use]
543 pub const fn is_empty(&self) -> bool {
544 self.non_empty_block_count == 0
545 }
546
547 #[must_use]
549 pub const fn is_randomly_ticking(&self) -> bool {
550 self.is_randomly_ticking_blocks() || self.is_randomly_ticking_fluids()
551 }
552
553 #[must_use]
555 pub const fn is_randomly_ticking_blocks(&self) -> bool {
556 self.ticking_block_count > 0
557 }
558
559 #[must_use]
561 pub const fn is_randomly_ticking_fluids(&self) -> bool {
562 self.ticking_fluid_count > 0
563 }
564
565 #[must_use]
567 pub fn maybe_has_block_light_sources(&self) -> bool {
568 !self.is_empty()
569 && self
570 .states
571 .maybe_has(|state| state.get_light_emission() > 0)
572 }
573
574 pub fn append_block_light_sources(
576 &self,
577 chunk_min_x: i32,
578 section_min_y: i32,
579 chunk_min_z: i32,
580 sources: &mut Vec<BlockPos>,
581 ) {
582 if !self.maybe_has_block_light_sources() {
583 return;
584 }
585
586 for local_index in 0..BlockPalette::VOLUME {
587 let state = self.states.get_at_index(local_index);
588 if state.get_light_emission() == 0 {
589 continue;
590 }
591
592 sources.push(BlockPos::new(
593 chunk_min_x + (local_index & 15) as i32,
594 section_min_y + (local_index >> 8) as i32,
595 chunk_min_z + ((local_index >> 4) & 15) as i32,
596 ));
597 }
598 }
599
600 #[must_use]
602 pub const fn non_empty_block_count(&self) -> u16 {
603 self.non_empty_block_count
604 }
605
606 #[must_use]
608 pub const fn fluid_count(&self) -> u16 {
609 self.fluid_count
610 }
611
612 #[must_use]
614 pub const fn has_fluid(&self) -> bool {
615 self.fluid_count > 0
616 }
617
618 #[must_use]
620 pub const fn ticking_block_count(&self) -> u16 {
621 self.ticking_block_count
622 }
623
624 #[must_use]
626 pub const fn ticking_fluid_count(&self) -> u16 {
627 self.ticking_fluid_count
628 }
629
630 pub fn recalculate_counts(&mut self) {
639 self.recalculate_counts_from_palette(Self::block_state_section_counts);
640 }
641
642 fn recalculate_counts_from_palette(
643 &mut self,
644 mut counts_for_state: impl FnMut(BlockStateId) -> BlockStateSectionCounts,
645 ) {
646 self.states.finalize_building();
647
648 let mut non_empty: u16 = 0;
649 let mut fluid: u16 = 0;
650 let mut ticking_blocks: u16 = 0;
651 let mut ticking_fluids: u16 = 0;
652
653 match &self.states {
654 BlockPalette::Homogeneous(state) => {
655 let counts = counts_for_state(*state);
656 Self::accumulate_counter_traits(
657 &mut non_empty,
658 &mut fluid,
659 &mut ticking_blocks,
660 &mut ticking_fluids,
661 counts,
662 BLOCKS_PER_SECTION,
663 );
664 }
665 BlockPalette::Heterogeneous(data) => {
666 for &(state, count) in &data.palette {
667 let counts = counts_for_state(state);
668 Self::accumulate_counter_traits(
669 &mut non_empty,
670 &mut fluid,
671 &mut ticking_blocks,
672 &mut ticking_fluids,
673 counts,
674 count,
675 );
676 }
677 }
678 BlockPalette::Building(_) => unreachable!("finalize_building was just called"),
679 }
680
681 self.non_empty_block_count = non_empty;
682 self.fluid_count = fluid;
683 self.ticking_block_count = ticking_blocks;
684 self.ticking_fluid_count = ticking_fluids;
685 }
686
687 const fn accumulate_counter_traits(
688 non_empty: &mut u16,
689 fluid: &mut u16,
690 ticking_blocks: &mut u16,
691 ticking_fluids: &mut u16,
692 counts: BlockStateSectionCounts,
693 block_count: u16,
694 ) {
695 if !counts.is_air {
696 *non_empty += block_count;
697 }
698 if counts.has_fluid {
699 *fluid += block_count;
700 }
701 if counts.randomly_ticking_block {
702 *ticking_blocks += block_count;
703 }
704 if counts.randomly_ticking_fluid {
705 *ticking_fluids += block_count;
706 }
707 }
708
709 #[must_use]
716 pub fn contains_poi(&self) -> bool {
717 let poi = ®ISTRY.poi_types;
718 match &self.states {
719 BlockPalette::Homogeneous(state) => poi.is_poi_state(*state),
720 BlockPalette::Heterogeneous(data) => data
721 .palette
722 .iter()
723 .any(|(state, _)| poi.is_poi_state(*state)),
724 BlockPalette::Building(_) => true,
727 }
728 }
729
730 pub fn set_block_state(
735 &mut self,
736 x: usize,
737 y: usize,
738 z: usize,
739 new_state: BlockStateId,
740 ) -> BlockStateId {
741 self.ensure_counter_ready_for_delta();
742 let old_state = self.states.set(x, y, z, new_state);
743
744 if old_state != new_state {
745 let old_counts = Self::block_state_section_counts(old_state);
746 let new_counts = Self::block_state_section_counts(new_state);
747 self.apply_count_change(old_counts, new_counts);
748 }
749
750 old_state
751 }
752
753 pub(crate) fn set_block_state_for_generation(
759 &mut self,
760 x: usize,
761 y: usize,
762 z: usize,
763 new_state: BlockStateId,
764 ) -> BlockStateId {
765 self.states.enter_building_mode();
766 self.states.set(x, y, z, new_state)
767 }
768
769 pub(crate) fn block_state_section_counts(state: BlockStateId) -> BlockStateSectionCounts {
771 let metadata = state.get_ticking_metadata();
772 BlockStateSectionCounts {
773 is_air: metadata.is_air(),
774 has_fluid: metadata.has_fluid(),
775 randomly_ticking_block: metadata.randomly_ticking_block(),
776 randomly_ticking_fluid: metadata.randomly_ticking_fluid(),
777 }
778 }
779
780 pub(crate) fn finalize_generation_counts_if_needed(&mut self) {
781 if matches!(&self.states, BlockPalette::Building(_)) {
782 self.recalculate_counts();
783 }
784 }
785
786 fn ensure_counter_ready_for_delta(&mut self) {
787 if matches!(&self.states, BlockPalette::Building(_)) {
788 log::debug!(
789 "finalizing worldgen Building palette before applying a counter-aware \
790 block-state delta"
791 );
792 self.recalculate_counts();
793 }
794 }
795
796 const fn apply_count_change(
797 &mut self,
798 old_counts: BlockStateSectionCounts,
799 new_counts: BlockStateSectionCounts,
800 ) {
801 if !old_counts.is_air && new_counts.is_air {
802 self.non_empty_block_count -= 1;
803 } else if old_counts.is_air && !new_counts.is_air {
804 self.non_empty_block_count += 1;
805 }
806
807 if old_counts.has_fluid && !new_counts.has_fluid {
808 self.fluid_count -= 1;
809 } else if !old_counts.has_fluid && new_counts.has_fluid {
810 self.fluid_count += 1;
811 }
812
813 if old_counts.randomly_ticking_block && !new_counts.randomly_ticking_block {
814 self.ticking_block_count -= 1;
815 } else if !old_counts.randomly_ticking_block && new_counts.randomly_ticking_block {
816 self.ticking_block_count += 1;
817 }
818
819 if old_counts.randomly_ticking_fluid && !new_counts.randomly_ticking_fluid {
820 self.ticking_fluid_count -= 1;
821 } else if !old_counts.randomly_ticking_fluid && new_counts.randomly_ticking_fluid {
822 self.ticking_fluid_count += 1;
823 }
824 }
825
826 pub fn write(&self, writer: &mut Cursor<Vec<u8>>) {
831 self.non_empty_block_count
832 .write(writer)
833 .expect("Failed to write block count");
834 self.fluid_count
835 .write(writer)
836 .expect("Failed to write fluid count");
837
838 self.states
839 .write(writer)
840 .expect("Failed to write block states");
841 self.biomes.write(writer).expect("Failed to write biomes");
842 }
843}
844
845#[cfg(test)]
846mod tests {
847 use steel_registry::init_vanilla_registry;
848 use steel_registry::vanilla_blocks;
849
850 use crate::behavior::init_behaviors;
851
852 use super::*;
853
854 fn plains_biomes() -> BiomePalette {
855 BiomePalette::Homogeneous(vanilla_biomes::PLAINS.id() as u16)
856 }
857
858 fn init_test_behaviors() {
859 init_vanilla_registry();
860 init_behaviors();
861 }
862
863 #[test]
864 fn recount_uses_homogeneous_palette_frequency() {
865 init_test_behaviors();
866
867 let mut section = ChunkSection::new_with_biomes(
868 BlockPalette::Homogeneous(vanilla_blocks::LAVA.default_state()),
869 plains_biomes(),
870 );
871
872 section.recalculate_counts();
873
874 assert_eq!(section.non_empty_block_count(), BLOCKS_PER_SECTION);
875 assert_eq!(section.fluid_count(), BLOCKS_PER_SECTION);
876 assert_eq!(section.ticking_block_count(), BLOCKS_PER_SECTION);
877 assert_eq!(section.ticking_fluid_count(), BLOCKS_PER_SECTION);
878 }
879
880 #[test]
881 fn recount_uses_heterogeneous_palette_frequencies() {
882 init_test_behaviors();
883
884 let air = vanilla_blocks::AIR.default_state();
885 let stone = vanilla_blocks::STONE.default_state();
886 let water = vanilla_blocks::WATER.default_state();
887 let lava = vanilla_blocks::LAVA.default_state();
888 let mut cube = Box::new([[[air; 16]; 16]; 16]);
889
890 cube[0][0][0] = stone;
891 cube[1][0][0] = stone;
892 cube[2][0][0] = water;
893 cube[3][0][0] = water;
894 cube[4][0][0] = water;
895 cube[5][0][0] = lava;
896
897 let mut section =
898 ChunkSection::new_with_biomes(BlockPalette::from_cube(cube), plains_biomes());
899
900 section.recalculate_counts();
901
902 assert_eq!(section.non_empty_block_count(), 6);
903 assert_eq!(section.fluid_count(), 4);
904 assert_eq!(section.ticking_block_count(), 1);
905 assert_eq!(section.ticking_fluid_count(), 1);
906 }
907
908 #[test]
909 fn holder_keeps_random_tick_eligibility_in_sync() {
910 init_test_behaviors();
911
912 let mut loaded_section = ChunkSection::new_with_biomes(
913 BlockPalette::Homogeneous(vanilla_blocks::LAVA.default_state()),
914 plains_biomes(),
915 );
916 loaded_section.recalculate_counts();
917 let loaded_holder = SectionHolder::new(loaded_section);
918 assert!(loaded_holder.is_randomly_ticking());
919
920 let holder = SectionHolder::new(ChunkSection::new_empty());
921 {
922 let mut section = holder.write();
923 section.set_block_state(0, 0, 0, vanilla_blocks::LAVA.default_state());
924 assert_eq!(section.ticking_block_count(), 1);
925 assert_eq!(section.ticking_fluid_count(), 1);
926 }
927 assert!(holder.is_randomly_ticking());
928
929 {
930 let Some(mut section) = holder.try_write() else {
931 panic!("uncontended section write lock was unavailable");
932 };
933 section.set_block_state(0, 0, 0, vanilla_blocks::AIR.default_state());
934 assert_eq!(section.ticking_block_count(), 0);
935 assert_eq!(section.ticking_fluid_count(), 0);
936 }
937 assert!(!holder.is_randomly_ticking());
938 }
939
940 #[test]
941 fn shared_random_tick_section_bits_follow_cross_word_updates() {
942 init_test_behaviors();
943 let sections = Sections::from_owned(
944 (0..65)
945 .map(|_| ChunkSection::new_empty())
946 .collect::<Vec<_>>()
947 .into_boxed_slice(),
948 );
949 let bits = Arc::clone(sections.random_tick_sections());
950 assert!(bits.is_empty());
951
952 {
953 let mut section = sections.sections[64].write();
954 section.set_block_state(0, 0, 0, vanilla_blocks::LAVA.default_state());
955 }
956 assert_eq!(bits.next(0), Some(64));
957
958 {
959 let mut section = sections.sections[1].write();
960 section.set_block_state(0, 0, 0, vanilla_blocks::LAVA.default_state());
961 }
962 assert_eq!(bits.next(0), Some(1));
963 assert_eq!(bits.next(2), Some(64));
964
965 {
966 let mut section = sections.sections[1].write();
967 section.set_block_state(0, 0, 0, vanilla_blocks::AIR.default_state());
968 }
969 assert_eq!(bits.next(0), Some(64));
970
971 {
972 let mut section = sections.sections[64].write();
973 section.set_block_state(0, 0, 0, vanilla_blocks::AIR.default_state());
974 }
975 assert!(bits.is_empty());
976 }
977
978 #[test]
979 fn generation_recount_publishes_random_tick_section_bit() {
980 init_test_behaviors();
981 let sections = Sections::from_owned(vec![ChunkSection::new_empty()].into_boxed_slice());
982 let bits = Arc::clone(sections.random_tick_sections());
983
984 {
985 let mut section = sections.sections[0].write();
986 section.set_block_state_for_generation(0, 0, 0, vanilla_blocks::LAVA.default_state());
987 }
988 assert!(bits.is_empty());
989
990 {
991 let mut section = sections.sections[0].write();
992 section.finalize_generation_counts_if_needed();
993 }
994 assert_eq!(bits.next(0), Some(0));
995 }
996
997 #[test]
998 fn counter_aware_write_recounts_building_palette_before_delta() {
999 init_test_behaviors();
1000
1001 let air = vanilla_blocks::AIR.default_state();
1002 let stone = vanilla_blocks::STONE.default_state();
1003 let mut section = ChunkSection::new_empty();
1004
1005 section.set_block_state_for_generation(0, 0, 0, stone);
1006 assert_eq!(section.non_empty_block_count(), 0);
1007
1008 let old_state = section.set_block_state(0, 0, 0, air);
1009
1010 assert_eq!(old_state, stone);
1011 assert_eq!(section.non_empty_block_count(), 0);
1012
1013 let old_state = section.set_block_state(0, 0, 0, stone);
1014
1015 assert_eq!(old_state, air);
1016 assert_eq!(section.non_empty_block_count(), 1);
1017 }
1018}