1use std::{
8 cell::RefCell,
9 sync::{Arc, Weak},
10 time::Instant,
11};
12
13use parking_lot::{RwLockReadGuard, RwLockWriteGuard};
14use simdnbt::owned::NbtCompound;
15use steel_registry::{
16 REGISTRY, block_entity_type::BlockEntityTypeRef, blocks::BlockRef,
17 blocks::block_state_ext::BlockStateExt as _, blocks::properties::Direction,
18 blocks::shapes::SupportType, fluid::FluidRef, vanilla_blocks,
19};
20use steel_utils::random::RandomSource;
21use steel_utils::{
22 BlockPos, BlockStateId, ChunkPos, PackedSectionBlockPos, SectionPos, types::UpdateFlags,
23};
24use steel_worldgen::structure::{StructureReferenceMap, StructureStartMap};
25
26use crate::behavior::{BLOCK_BEHAVIORS, FLUID_BEHAVIORS};
27use crate::block_entity::{BLOCK_ENTITIES, SharedBlockEntity};
28use crate::chunk::{
29 Chunk,
30 chunk_generation_task::StaticCache2D,
31 chunk_holder::ChunkHolder,
32 chunk_pyramid::ChunkStep,
33 full_chunk::FullChunkRef,
34 heightmap::{Heightmap, HeightmapType},
35 section::{ChunkSection, SectionHolder, SectionWriteGuard, Sections},
36 status::ChunkStatus,
37};
38use crate::entity::SharedEntity;
39use crate::world::tick_scheduler::TickPriority;
40use crate::world::{LevelAccessor, LevelReader, ScheduledTickAccess, World};
41use crate::worldgen::feature::instrumentation::OreFeatureStats;
42use crate::worldgen::generator::context::WorldGenContext;
43
44pub struct WorldGenRegion<'a> {
51 context: &'a WorldGenContext,
52 step: &'a ChunkStep,
53 cache: &'a StaticCache2D<Arc<ChunkHolder>>,
54 center: ChunkPos,
55 chunk_cache_radius: i32,
56 chunks: RefCell<Box<[Option<CachedWorldGenChunk<'a>>]>>,
57 worldgen_heightmaps: RefCell<Box<[CachedWorldgenHeightmaps]>>,
58 random: RandomSource,
59}
60
61pub(crate) struct WorldGenBulkSectionAccess<'region, 'world, 'profile> {
68 region: &'region WorldGenRegion<'world>,
69 chunk_cache_radius: i32,
70 chunks: Box<[Option<CachedWorldGenChunk<'region>>]>,
71 air: BlockStateId,
72 ore_profile: Option<&'profile RefCell<OreFeatureStats>>,
73}
74
75struct CachedWorldGenChunk<'region> {
76 holder: &'region ChunkHolder,
77 chunk: &'region Chunk,
78 verified_status: ChunkStatus,
79 access_mode: WorldGenAccessMode,
80}
81
82#[derive(Clone, Copy)]
88pub(crate) struct WorldGenChunkRef<'a> {
89 holder: &'a ChunkHolder,
90 chunk: &'a Chunk,
91 access_mode: WorldGenAccessMode,
92}
93
94impl WorldGenChunkRef<'_> {
95 fn published_status(self) -> ChunkStatus {
96 let Some(status) = self.holder.published_status() else {
97 panic!("worldgen chunk data cannot lose its published status");
98 };
99 status
100 }
101
102 #[must_use]
103 pub(crate) const fn sections(&self) -> &Sections {
104 self.chunk.sections()
105 }
106
107 pub(crate) fn structure_starts_mut(&self) -> RwLockWriteGuard<'_, StructureStartMap> {
108 self.chunk.structure_starts_mut()
109 }
110
111 pub(crate) fn structure_references(&self) -> RwLockReadGuard<'_, StructureReferenceMap> {
112 self.chunk.structure_references()
113 }
114
115 fn full_imposter_height_at(
116 self,
117 heightmap_type: HeightmapType,
118 local_x: usize,
119 local_z: usize,
120 ) -> i32 {
121 let mapped_type = match heightmap_type {
122 HeightmapType::WorldSurfaceWg => HeightmapType::WorldSurface,
123 HeightmapType::OceanFloorWg => HeightmapType::OceanFloor,
124 other => other,
125 };
126 FullChunkRef::from_full_context(self.chunk).get_height(mapped_type, local_x, local_z)
127 }
128}
129
130#[derive(Clone, Copy, PartialEq, Eq)]
137enum WorldGenAccessMode {
138 WritableProto,
139 ReadOnlyFull,
140}
141
142impl WorldGenAccessMode {
143 const fn capture(status: ChunkStatus) -> Self {
144 if matches!(status, ChunkStatus::Full) {
145 Self::ReadOnlyFull
146 } else {
147 Self::WritableProto
148 }
149 }
150
151 const fn allows_writes(self) -> bool {
152 matches!(self, Self::WritableProto)
153 }
154}
155
156#[derive(Default)]
157struct CachedWorldgenHeightmaps {
158 world_surface_wg: Option<Box<[i32; 256]>>,
159 ocean_floor_wg: Option<Box<[i32; 256]>>,
160}
161
162impl CachedWorldgenHeightmaps {
163 const fn supports(heightmap_type: HeightmapType) -> bool {
164 matches!(
165 heightmap_type,
166 HeightmapType::WorldSurfaceWg | HeightmapType::OceanFloorWg
167 )
168 }
169
170 fn get(&self, heightmap_type: HeightmapType) -> Option<&[i32; 256]> {
171 match heightmap_type {
172 HeightmapType::WorldSurfaceWg => self.world_surface_wg.as_deref(),
173 HeightmapType::OceanFloorWg => self.ocean_floor_wg.as_deref(),
174 _ => None,
175 }
176 }
177
178 fn set(&mut self, heightmap_type: HeightmapType, columns: Box<[i32; 256]>) {
179 match heightmap_type {
180 HeightmapType::WorldSurfaceWg => self.world_surface_wg = Some(columns),
181 HeightmapType::OceanFloorWg => self.ocean_floor_wg = Some(columns),
182 _ => {}
183 }
184 }
185}
186
187#[derive(Clone, Copy, PartialEq, Eq)]
188struct WritableSectionKey {
189 chunk_x: i32,
190 chunk_z: i32,
191 status: ChunkStatus,
192 section_index: usize,
193}
194
195impl<'a> WorldGenRegion<'a> {
196 #[must_use]
198 pub fn new(
199 context: &'a WorldGenContext,
200 step: &'a ChunkStep,
201 cache: &'a StaticCache2D<Arc<ChunkHolder>>,
202 center: ChunkPos,
203 random: RandomSource,
204 ) -> Self {
205 let chunk_cache_radius =
206 i32::try_from(step.direct_dependencies.get_radius()).unwrap_or(i32::MAX);
207 let chunk_cache_size = chunk_cache_radius.saturating_mul(2).saturating_add(1);
208 let chunk_cache_len =
209 usize::try_from(chunk_cache_size.saturating_mul(chunk_cache_size)).unwrap_or(0);
210 let chunks = (0..chunk_cache_len).map(|_| None).collect();
211 let worldgen_heightmaps = (0..chunk_cache_len)
212 .map(|_| CachedWorldgenHeightmaps::default())
213 .collect();
214
215 Self {
216 context,
217 step,
218 cache,
219 center,
220 chunk_cache_radius,
221 chunks: RefCell::new(chunks),
222 worldgen_heightmaps: RefCell::new(worldgen_heightmaps),
223 random,
224 }
225 }
226
227 #[must_use]
229 pub const fn center(&self) -> ChunkPos {
230 self.center
231 }
232
233 pub const fn random_mut(&mut self) -> &mut RandomSource {
235 &mut self.random
236 }
237
238 #[must_use]
240 pub const fn min_y(&self) -> i32 {
241 self.context.min_y()
242 }
243
244 #[must_use]
246 pub const fn height(&self) -> i32 {
247 self.context.height()
248 }
249
250 #[must_use]
252 pub fn generation_min_y(&self) -> i32 {
253 self.context.generation_min_y()
254 }
255
256 #[must_use]
258 pub fn generation_height(&self) -> i32 {
259 self.context.generation_height()
260 }
261
262 #[must_use]
264 pub const fn sea_level(&self) -> i32 {
265 self.context.sea_level()
266 }
267
268 #[must_use]
270 pub fn seed(&self) -> i64 {
271 self.context.world().seed()
272 }
273
274 #[must_use]
276 pub fn weak_world(&self) -> Weak<World> {
277 self.context.weak_world()
278 }
279
280 #[must_use]
286 #[expect(
287 clippy::unused_self,
288 reason = "keeps light lookup callable through the region instance"
289 )]
290 pub const fn block_light_at(&self, _pos: BlockPos) -> u8 {
291 0
292 }
293
294 #[must_use]
296 pub const fn max_y_exclusive(&self) -> i32 {
297 self.min_y() + self.height()
298 }
299
300 #[must_use]
302 pub const fn is_outside_build_height(&self, y: i32) -> bool {
303 y < self.min_y() || y >= self.max_y_exclusive()
304 }
305
306 #[must_use]
308 pub const fn required_status_at(&self, chunk_x: i32, chunk_z: i32) -> Option<ChunkStatus> {
309 self.step
310 .direct_dependencies
311 .get(Self::chessboard_distance(self.center, chunk_x, chunk_z))
312 }
313
314 #[must_use]
316 pub const fn can_write_to_chunk(&self, chunk_x: i32, chunk_z: i32) -> bool {
317 let radius = self.step.block_state_write_radius;
318 radius >= 0
319 && (chunk_x - self.center.0.x).abs() <= radius
320 && (chunk_z - self.center.0.y).abs() <= radius
321 }
322
323 #[must_use]
325 pub(crate) fn try_chunk(
326 &self,
327 chunk_x: i32,
328 chunk_z: i32,
329 status: ChunkStatus,
330 ) -> Option<WorldGenChunkRef<'a>> {
331 let available_status = self.required_status_at(chunk_x, chunk_z)?;
332 if status > available_status {
333 return None;
334 }
335
336 let holder = self.cache.get(chunk_x, chunk_z);
337 let chunk = holder.try_chunk(available_status)?;
338 let published_status = holder.published_status()?;
339 Some(WorldGenChunkRef {
340 holder,
341 chunk,
342 access_mode: WorldGenAccessMode::capture(published_status),
343 })
344 }
345
346 pub(crate) fn chunk(
353 &self,
354 chunk_x: i32,
355 chunk_z: i32,
356 status: ChunkStatus,
357 ) -> WorldGenChunkRef<'a> {
358 let Some(chunk) = self.try_chunk(chunk_x, chunk_z, status) else {
359 let available = self.required_status_at(chunk_x, chunk_z);
360 panic!(
361 "Worldgen requested chunk ({chunk_x}, {chunk_z}) at status {status:?}, \
362 but the {:?} step only provides {available:?} at that distance from ({}, {})",
363 self.step.target_status, self.center.0.x, self.center.0.y
364 );
365 };
366
367 chunk
368 }
369
370 fn with_cached_chunk<R>(
371 &self,
372 chunk_x: i32,
373 chunk_z: i32,
374 status: ChunkStatus,
375 f: impl FnOnce(WorldGenChunkRef<'_>) -> R,
376 ) -> R {
377 let Some(cache_index) = self.chunk_cache_index(chunk_x, chunk_z) else {
378 let chunk = self.chunk(chunk_x, chunk_z, status);
379 return f(chunk);
380 };
381
382 let cache_needs_update = self.chunks.borrow().get(cache_index).is_none_or(|cached| {
383 cached
384 .as_ref()
385 .is_none_or(|cached| status > cached.verified_status)
386 });
387
388 if cache_needs_update {
389 let chunk = self.chunk(chunk_x, chunk_z, status);
390 let mut chunks = self.chunks.borrow_mut();
391 let Some(slot) = chunks.get_mut(cache_index) else {
392 panic!("Worldgen region cache index {cache_index} escaped its storage");
393 };
394 if let Some(cached) = slot {
395 cached.verified_status = status;
396 } else {
397 *slot = Some(CachedWorldGenChunk {
398 access_mode: chunk.access_mode,
399 holder: chunk.holder,
400 chunk: chunk.chunk,
401 verified_status: status,
402 });
403 }
404 }
405
406 let chunks = self.chunks.borrow();
407 let Some(Some(cached)) = chunks.get(cache_index) else {
408 panic!("Worldgen region cache failed to store chunk ({chunk_x}, {chunk_z})");
409 };
410 f(WorldGenChunkRef {
411 holder: cached.holder,
412 chunk: cached.chunk,
413 access_mode: cached.access_mode,
414 })
415 }
416
417 fn chunk_cache_index(&self, chunk_x: i32, chunk_z: i32) -> Option<usize> {
418 let radius = self.chunk_cache_radius;
419 let size = radius.checked_mul(2)?.checked_add(1)?;
420 let rel_x = chunk_x.checked_sub(self.center.0.x)?.checked_add(radius)?;
421 let rel_z = chunk_z.checked_sub(self.center.0.y)?.checked_add(radius)?;
422 if rel_x < 0 || rel_x >= size || rel_z < 0 || rel_z >= size {
423 return None;
424 }
425
426 usize::try_from(rel_z.checked_mul(size)?.checked_add(rel_x)?).ok()
427 }
428
429 #[must_use]
434 pub fn block_state(&self, pos: BlockPos) -> BlockStateId {
435 let chunk_x = SectionPos::block_to_section_coord(pos.x());
436 let chunk_z = SectionPos::block_to_section_coord(pos.z());
437 self.with_cached_chunk(chunk_x, chunk_z, ChunkStatus::Empty, |chunk| {
438 chunk.chunk.get_block_state(pos)
439 })
440 }
441
442 #[must_use]
447 pub fn block_entity(&self, pos: BlockPos) -> Option<SharedBlockEntity> {
448 let chunk_x = SectionPos::block_to_section_coord(pos.x());
449 let chunk_z = SectionPos::block_to_section_coord(pos.z());
450 self.with_cached_chunk(chunk_x, chunk_z, ChunkStatus::Empty, |chunk| {
451 let block_entity = match chunk.access_mode {
452 WorldGenAccessMode::ReadOnlyFull => {
453 FullChunkRef::from_full_context(chunk.chunk).get_block_entity(pos)
454 }
455 WorldGenAccessMode::WritableProto => chunk
456 .chunk
457 .get_block_entity(pos)
458 .or_else(|| chunk.chunk.promote_pending_block_entity(pos)),
459 };
460 if block_entity.is_none() && chunk.chunk.get_block_state(pos).has_block_entity() {
461 log::warn!("Tried to access a block entity before it was created at {pos:?}");
462 }
463 block_entity
464 })
465 }
466
467 #[must_use]
475 pub fn noise_biome_id(&self, quart_x: i32, quart_y: i32, quart_z: i32) -> u16 {
476 let chunk_x = quart_x >> 2;
477 let chunk_z = quart_z >> 2;
478 let local_quart_x = (quart_x & 3) as usize;
479 let local_quart_z = (quart_z & 3) as usize;
480
481 self.with_cached_chunk(chunk_x, chunk_z, ChunkStatus::Biomes, |chunk| {
482 let sections = chunk.chunk.sections();
483 let (section_index, local_quart_y) =
484 Self::biome_quart_y_indices(self.min_y(), sections.sections.len(), quart_y);
485 let section = §ions.sections[section_index];
486
487 section
488 .read()
489 .biomes
490 .get(local_quart_x, local_quart_y, local_quart_z)
491 })
492 }
493
494 #[must_use]
504 pub fn set_block_state(&self, pos: BlockPos, state: BlockStateId, flags: UpdateFlags) -> bool {
505 let Some((chunk_x, chunk_z, status)) = self.writable_chunk_for_pos(pos, "write block")
506 else {
507 return false;
508 };
509 let changed_proto = self.with_cached_chunk(chunk_x, chunk_z, status, |chunk| {
510 if !chunk.access_mode.allows_writes() {
511 return false;
514 }
515 let old_state = chunk.chunk.set_block_state_for_generation(
516 chunk.published_status(),
517 pos,
518 state,
519 flags,
520 );
521 if state.has_block_entity() {
522 chunk.chunk.set_pending_block_entity_if_state(pos, state);
523 } else if old_state.is_some_and(|old_state| old_state.has_block_entity()) {
524 chunk.chunk.remove_block_entity_if_state(pos, state);
525 }
526 old_state.is_some()
527 });
528 if changed_proto {
529 self.invalidate_cached_worldgen_heightmaps(chunk_x, chunk_z);
530 }
531 if !flags.contains(UpdateFlags::UPDATE_KNOWN_SHAPE)
532 && let Some(postprocess_pos) = Self::postprocess_pos_for_state(state, pos)
533 {
534 self.mark_pos_for_postprocessing(postprocess_pos);
535 }
536 true
537 }
538
539 fn postprocess_pos_for_state(state: BlockStateId, pos: BlockPos) -> Option<BlockPos> {
544 let block = state.get_block();
545 if block == &vanilla_blocks::BROWN_MUSHROOM || block == &vanilla_blocks::RED_MUSHROOM {
546 Some(pos)
547 } else if block == &vanilla_blocks::SOUL_SAND || block == &vanilla_blocks::MAGMA_BLOCK {
548 Some(pos.above())
549 } else {
550 None
551 }
552 }
553
554 #[must_use]
560 pub fn set_block_entity_data(
561 &self,
562 pos: BlockPos,
563 block_entity_type: BlockEntityTypeRef,
564 state: BlockStateId,
565 nbt: NbtCompound,
566 ) -> bool {
567 let Some((chunk_x, chunk_z, status)) =
568 self.writable_chunk_for_pos(pos, "write block entity")
569 else {
570 return false;
571 };
572 if !block_entity_type.is_valid(state.get_block()) {
573 log::warn!(
574 "Worldgen block entity {} at {pos:?} does not accept block {}",
575 block_entity_type.key,
576 state.get_block().key,
577 );
578 return false;
579 }
580
581 self.with_cached_chunk(chunk_x, chunk_z, status, |chunk| {
582 if !chunk.access_mode.allows_writes() {
583 return false;
584 }
585 let entity = BLOCK_ENTITIES.create_and_load_owned_or_raw(
586 block_entity_type,
587 chunk.chunk.level_weak(),
588 pos,
589 state,
590 nbt,
591 );
592 chunk.chunk.set_block_entity(entity)
593 })
594 }
595
596 #[must_use]
598 pub fn remove_block_entity(&self, pos: BlockPos) -> bool {
599 let Some((chunk_x, chunk_z, status)) =
600 self.writable_chunk_for_pos(pos, "remove block entity")
601 else {
602 return false;
603 };
604
605 self.with_cached_chunk(chunk_x, chunk_z, status, |chunk| {
606 if chunk.access_mode.allows_writes() {
607 chunk.chunk.remove_block_entity(pos);
608 }
609 });
610 true
611 }
612
613 #[must_use]
618 pub fn add_fresh_entity(&self, entity: SharedEntity) -> bool {
619 let pos = BlockPos::from(entity.position());
620 let (chunk_x, chunk_z, status) = self.dependency_chunk_for_pos(pos, "add entity");
621
622 self.with_cached_chunk(chunk_x, chunk_z, status, |chunk| {
623 if chunk.access_mode.allows_writes() {
624 chunk.chunk.add_entity(entity)
625 } else {
626 drop(entity);
627 true
628 }
629 })
630 }
631
632 #[must_use]
638 pub fn schedule_block_tick(
639 &self,
640 pos: BlockPos,
641 block: BlockRef,
642 _delay: i32,
643 priority: TickPriority,
644 ) -> bool {
645 let (chunk_x, chunk_z, status) = self.dependency_chunk_for_pos(pos, "schedule block tick");
646 self.with_cached_chunk(chunk_x, chunk_z, status, |chunk| {
647 if chunk.access_mode.allows_writes() {
648 chunk.chunk.schedule_block_tick(pos, block, priority);
649 }
650 });
651 true
652 }
653
654 #[must_use]
656 pub fn schedule_block_tick_default(&self, pos: BlockPos, block: BlockRef, delay: i32) -> bool {
657 self.schedule_block_tick(pos, block, delay, TickPriority::Normal)
658 }
659
660 #[must_use]
664 pub fn schedule_fluid_tick(
665 &self,
666 pos: BlockPos,
667 fluid: FluidRef,
668 _delay: i32,
669 priority: TickPriority,
670 ) -> bool {
671 let (chunk_x, chunk_z, status) = self.dependency_chunk_for_pos(pos, "schedule fluid tick");
672 self.with_cached_chunk(chunk_x, chunk_z, status, |chunk| {
673 if chunk.access_mode.allows_writes() {
674 chunk.chunk.schedule_fluid_tick(pos, fluid, priority);
675 }
676 });
677 true
678 }
679
680 #[must_use]
682 pub fn schedule_fluid_tick_default(&self, pos: BlockPos, fluid: FluidRef, delay: i32) -> bool {
683 self.schedule_fluid_tick(pos, fluid, delay, TickPriority::Normal)
684 }
685
686 pub fn mark_pos_for_postprocessing(&self, pos: BlockPos) {
691 let chunk_x = SectionPos::block_to_section_coord(pos.x());
692 let chunk_z = SectionPos::block_to_section_coord(pos.z());
693 self.with_cached_chunk(chunk_x, chunk_z, ChunkStatus::Empty, |chunk| {
694 if chunk.access_mode.allows_writes() {
695 chunk.chunk.mark_pos_for_postprocessing(pos);
696 }
697 });
698 }
699
700 #[must_use]
706 pub fn height_at(&self, heightmap_type: HeightmapType, x: i32, z: i32) -> i32 {
707 let chunk_x = SectionPos::block_to_section_coord(x);
708 let chunk_z = SectionPos::block_to_section_coord(z);
709 let local_x = (x & 15) as usize;
710 let local_z = (z & 15) as usize;
711 self.with_cached_chunk(chunk_x, chunk_z, ChunkStatus::Empty, |chunk| {
712 match chunk.access_mode {
713 WorldGenAccessMode::ReadOnlyFull => {
717 chunk.full_imposter_height_at(heightmap_type, local_x, local_z)
718 }
719 WorldGenAccessMode::WritableProto => self.cached_proto_height_at(
720 chunk.chunk,
721 heightmap_type,
722 chunk_x,
723 chunk_z,
724 local_x + local_z * 16,
725 ),
726 }
727 })
728 }
729
730 fn cached_proto_height_at(
731 &self,
732 proto: &Chunk,
733 heightmap_type: HeightmapType,
734 chunk_x: i32,
735 chunk_z: i32,
736 column_index: usize,
737 ) -> i32 {
738 if !CachedWorldgenHeightmaps::supports(heightmap_type) {
739 return proto.generation_height_at(
740 heightmap_type,
741 column_index % 16,
742 column_index / 16,
743 );
744 }
745 let Some(cache_index) = self.chunk_cache_index(chunk_x, chunk_z) else {
746 return proto.generation_height_at(
747 heightmap_type,
748 column_index % 16,
749 column_index / 16,
750 );
751 };
752
753 {
754 let heightmaps = self.worldgen_heightmaps.borrow();
755 let Some(cached) = heightmaps.get(cache_index) else {
756 panic!("Worldgen heightmap cache index {cache_index} escaped its storage");
757 };
758 if let Some(columns) = cached.get(heightmap_type) {
759 return columns[column_index];
760 }
761 }
762
763 let columns = Self::proto_heightmap_columns(proto, heightmap_type);
764 let height = columns[column_index];
765 let mut heightmaps = self.worldgen_heightmaps.borrow_mut();
766 let Some(cached) = heightmaps.get_mut(cache_index) else {
767 panic!("Worldgen heightmap cache index {cache_index} escaped its storage");
768 };
769 cached.set(heightmap_type, columns);
770 height
771 }
772
773 fn proto_heightmap_columns(proto: &Chunk, heightmap_type: HeightmapType) -> Box<[i32; 256]> {
774 {
775 let heightmaps = proto.heightmaps.read();
776 if let Some(heightmap) = heightmaps.get(heightmap_type) {
777 return Self::copy_heightmap_columns(heightmap, proto.min_y());
778 }
779 }
780
781 let mut heightmaps = proto.heightmaps.write();
782 heightmaps.prime_from_sections(
783 &[heightmap_type],
784 proto.min_y(),
785 proto.height(),
786 &proto.sections.sections,
787 );
788 let Some(heightmap) = heightmaps.get(heightmap_type) else {
789 panic!("heightmap {heightmap_type:?} missing after priming");
790 };
791
792 Self::copy_heightmap_columns(heightmap, proto.min_y())
793 }
794
795 fn invalidate_cached_worldgen_heightmaps(&self, chunk_x: i32, chunk_z: i32) {
796 let Some(cache_index) = self.chunk_cache_index(chunk_x, chunk_z) else {
797 return;
798 };
799 let mut heightmaps = self.worldgen_heightmaps.borrow_mut();
800 let Some(cached) = heightmaps.get_mut(cache_index) else {
801 panic!("Worldgen heightmap cache index {cache_index} escaped its storage");
802 };
803 *cached = CachedWorldgenHeightmaps::default();
804 }
805
806 fn copy_heightmap_columns(heightmap: &Heightmap, min_y: i32) -> Box<[i32; 256]> {
807 let mut columns = Box::new([0; 256]);
808 for (index, &height) in heightmap.raw_data().iter().enumerate() {
809 columns[index] = i32::from(height) + min_y;
810 }
811 columns
812 }
813
814 pub(crate) fn bulk_section_access_for_ore<'profile>(
815 &self,
816 profile: Option<&'profile RefCell<OreFeatureStats>>,
817 ) -> WorldGenBulkSectionAccess<'_, 'a, 'profile> {
818 WorldGenBulkSectionAccess::new(self, profile)
819 }
820
821 fn writable_chunk_for_pos(
822 &self,
823 pos: BlockPos,
824 action: &str,
825 ) -> Option<(i32, i32, ChunkStatus)> {
826 let chunk_x = SectionPos::block_to_section_coord(pos.x());
827 let chunk_z = SectionPos::block_to_section_coord(pos.z());
828
829 if !self.can_write_to_chunk(chunk_x, chunk_z) {
830 log::error!(
831 "Worldgen attempted to {action} at ({}, {}, {}) outside {:?} write radius {} centered on ({}, {})",
832 pos.x(),
833 pos.y(),
834 pos.z(),
835 self.step.target_status,
836 self.step.block_state_write_radius,
837 self.center.0.x,
838 self.center.0.y,
839 );
840 return None;
841 }
842
843 let Some(status) = self.required_status_at(chunk_x, chunk_z) else {
844 panic!(
845 "Worldgen attempted to {action} at ({}, {}, {}) in chunk ({chunk_x}, {chunk_z}), \
846 but {:?} declares no direct dependency for that chunk",
847 pos.x(),
848 pos.y(),
849 pos.z(),
850 self.step.target_status,
851 );
852 };
853
854 Some((chunk_x, chunk_z, status))
855 }
856
857 fn dependency_chunk_for_pos(&self, pos: BlockPos, action: &str) -> (i32, i32, ChunkStatus) {
858 let chunk_x = SectionPos::block_to_section_coord(pos.x());
859 let chunk_z = SectionPos::block_to_section_coord(pos.z());
860 let Some(status) = self.required_status_at(chunk_x, chunk_z) else {
861 panic!(
862 "Worldgen attempted to {action} at ({}, {}, {}) in chunk ({chunk_x}, {chunk_z}), \
863 but {:?} declares no direct dependency for that chunk",
864 pos.x(),
865 pos.y(),
866 pos.z(),
867 self.step.target_status,
868 );
869 };
870
871 (chunk_x, chunk_z, status)
872 }
873
874 const fn chessboard_distance(center: ChunkPos, chunk_x: i32, chunk_z: i32) -> usize {
875 let dx = abs_diff(center.0.x, chunk_x);
876 let dz = abs_diff(center.0.y, chunk_z);
877 if dx > dz { dx as usize } else { dz as usize }
878 }
879
880 fn biome_quart_y_indices(min_y: i32, section_count: usize, quart_y: i32) -> (usize, usize) {
881 let Some(total_quart_y) = section_count.checked_mul(4) else {
882 panic!("Worldgen chunk section count {section_count} overflows biome quart range");
883 };
884 assert!(
885 total_quart_y > 0,
886 "Worldgen chunk must have at least one biome section"
887 );
888
889 let relative_quart_y = i64::from(quart_y) - i64::from(min_y >> 2);
890 let max_relative_quart_y = total_quart_y - 1;
891 let clamped_relative_quart_y = if relative_quart_y <= 0 {
892 0
893 } else {
894 usize::try_from(relative_quart_y).map_or(max_relative_quart_y, |relative| {
895 relative.min(max_relative_quart_y)
896 })
897 };
898
899 (clamped_relative_quart_y / 4, clamped_relative_quart_y & 3)
900 }
901}
902
903impl<'region, 'world, 'profile> WorldGenBulkSectionAccess<'region, 'world, 'profile> {
904 fn new(
905 region: &'region WorldGenRegion<'world>,
906 ore_profile: Option<&'profile RefCell<OreFeatureStats>>,
907 ) -> Self {
908 let chunk_cache_radius = region.chunk_cache_radius;
909 let chunk_cache_size = chunk_cache_radius.saturating_mul(2).saturating_add(1);
910 let chunk_cache_len =
911 usize::try_from(chunk_cache_size.saturating_mul(chunk_cache_size)).unwrap_or(0);
912 let chunks = (0..chunk_cache_len).map(|_| None).collect();
913
914 Self {
915 region,
916 chunk_cache_radius,
917 chunks,
918 air: REGISTRY.blocks.get_default_state_id(&vanilla_blocks::AIR),
919 ore_profile,
920 }
921 }
922
923 pub(crate) fn record_ore_candidate_position(&mut self) {
924 self.with_ore_profile(OreFeatureStats::record_candidate_position);
925 }
926
927 pub(crate) fn record_ore_unique_position(&mut self) {
928 self.with_ore_profile(OreFeatureStats::record_unique_position);
929 }
930
931 pub(crate) fn record_ore_write_allowed_position(&mut self) {
932 self.with_ore_profile(OreFeatureStats::record_write_allowed_position);
933 }
934
935 pub(crate) fn ore_target_block_state(&mut self, pos: BlockPos) -> BlockStateId {
936 self.with_ore_profile(OreFeatureStats::record_target_read);
937 self.block_state(pos)
938 }
939
940 pub(crate) fn ore_neighbor_block_state(&mut self, pos: BlockPos) -> BlockStateId {
941 self.with_ore_profile(OreFeatureStats::record_neighbor_read);
942 self.block_state(pos)
943 }
944
945 #[must_use]
950 pub(crate) fn replace_ore_target_block_state(
951 &mut self,
952 pos: BlockPos,
953 replacement: impl FnOnce(BlockStateId) -> Option<BlockStateId>,
954 ) -> bool {
955 self.with_ore_profile(OreFeatureStats::record_target_read);
956 let ore_profile = self.ore_profile;
957 let started_at = ore_profile.map(|_| Instant::now());
958 let Some(key) = self.writable_section_key(pos) else {
959 Self::record_ore_write_time(ore_profile, started_at);
960 return false;
961 };
962
963 let chunk = self.chunk(key.chunk_x, key.chunk_z, key.status);
964 if !chunk.access_mode.allows_writes() {
965 Self::record_ore_write_time(ore_profile, started_at);
966 return false;
967 }
968 let Some(section) = chunk.chunk.sections().sections.get(key.section_index) else {
969 panic!(
970 "Worldgen bulk section write at ({}, {}, {}) resolved missing section index {}",
971 pos.x(),
972 pos.y(),
973 pos.z(),
974 key.section_index
975 );
976 };
977
978 let mut section_guard = Self::ore_section_write_guard(ore_profile, section, key);
979
980 let local_x = Self::local_coord(pos.x());
981 let local_y = Self::local_coord(pos.y());
982 let local_z = Self::local_coord(pos.z());
983 let old_state = section_guard.states.get(local_x, local_y, local_z);
984 let Some(state) = replacement(old_state) else {
985 Self::record_ore_write_time(ore_profile, started_at);
986 return false;
987 };
988
989 let old_state = Self::set_bulk_block_state(
990 chunk.holder,
991 &mut section_guard,
992 local_x,
993 local_y,
994 local_z,
995 state,
996 );
997 Self::with_ore_profile_ref(ore_profile, OreFeatureStats::record_write);
998 if old_state != state {
999 chunk.chunk.mark_dirty();
1000 }
1001
1002 Self::record_ore_write_time(ore_profile, started_at);
1003 true
1004 }
1005
1006 pub(crate) fn replace_ore_target_block_states_in_section(
1008 &mut self,
1009 chunk_x: i32,
1010 chunk_z: i32,
1011 section_index: usize,
1012 positions: &[PackedSectionBlockPos],
1013 mut replacement: impl FnMut(BlockStateId) -> Option<BlockStateId>,
1014 ) -> u64 {
1015 let ore_profile = self.ore_profile;
1016 let started_at = ore_profile.map(|_| Instant::now());
1017 if !self.region.can_write_to_chunk(chunk_x, chunk_z) {
1018 Self::record_ore_write_time(ore_profile, started_at);
1019 return 0;
1020 }
1021 Self::with_ore_profile_ref(ore_profile, |profile| {
1022 profile.record_write_allowed_positions(positions.len() as u64);
1023 });
1024 let Some(status) = self.region.required_status_at(chunk_x, chunk_z) else {
1025 panic!(
1026 "Worldgen attempted to bulk write ore in chunk ({chunk_x}, {chunk_z}), \
1027 but {:?} declares no direct dependency for that chunk",
1028 self.region.step.target_status,
1029 );
1030 };
1031 let key = WritableSectionKey {
1032 chunk_x,
1033 chunk_z,
1034 status,
1035 section_index,
1036 };
1037 let chunk = self.chunk(chunk_x, chunk_z, status);
1038 if !chunk.access_mode.allows_writes() {
1039 Self::record_ore_write_time(ore_profile, started_at);
1040 return 0;
1041 }
1042 let Some(section) = chunk.chunk.sections().sections.get(section_index) else {
1043 panic!(
1044 "Worldgen bulk section write in chunk ({chunk_x}, {chunk_z}) resolved missing section index {section_index}",
1045 );
1046 };
1047
1048 let mut section_guard = Self::ore_section_write_guard(ore_profile, section, key);
1049 let mut placed = 0_u64;
1050 let mut dirty = false;
1051
1052 if let Some(profile) = ore_profile {
1053 for &pos in positions {
1054 Self::with_ore_profile_ref(Some(profile), OreFeatureStats::record_target_read);
1055 let local_x = usize::from(pos.x());
1056 let local_y = usize::from(pos.y());
1057 let local_z = usize::from(pos.z());
1058 let old_state = section_guard.states.get(local_x, local_y, local_z);
1059 if let Some(state) = replacement(old_state) {
1060 let old_state = Self::set_bulk_block_state(
1061 chunk.holder,
1062 &mut section_guard,
1063 local_x,
1064 local_y,
1065 local_z,
1066 state,
1067 );
1068 Self::with_ore_profile_ref(Some(profile), OreFeatureStats::record_write);
1069 dirty |= old_state != state;
1070 placed += 1;
1071 }
1072 }
1073 } else {
1074 for &pos in positions {
1075 let local_x = usize::from(pos.x());
1076 let local_y = usize::from(pos.y());
1077 let local_z = usize::from(pos.z());
1078 let old_state = section_guard.states.get(local_x, local_y, local_z);
1079 if let Some(state) = replacement(old_state) {
1080 let old_state = Self::set_bulk_block_state(
1081 chunk.holder,
1082 &mut section_guard,
1083 local_x,
1084 local_y,
1085 local_z,
1086 state,
1087 );
1088 dirty |= old_state != state;
1089 placed += 1;
1090 }
1091 }
1092 }
1093
1094 drop(section_guard);
1095 if dirty {
1096 chunk.chunk.mark_dirty();
1097 }
1098
1099 Self::record_ore_write_time(ore_profile, started_at);
1100 placed
1101 }
1102
1103 #[must_use]
1107 pub(crate) fn block_state(&mut self, pos: BlockPos) -> BlockStateId {
1108 let ore_profile = self.ore_profile;
1109 let started_at = ore_profile.map(|_| Instant::now());
1110 let air = self.air;
1111 let Some(section_index) =
1112 Self::section_index(self.region.min_y(), self.region.height(), pos.y())
1113 else {
1114 Self::record_ore_read_time(ore_profile, started_at);
1115 return air;
1116 };
1117
1118 let chunk_x = SectionPos::block_to_section_coord(pos.x());
1119 let chunk_z = SectionPos::block_to_section_coord(pos.z());
1120 let chunk = self.chunk(chunk_x, chunk_z, ChunkStatus::Empty);
1121 let Some(section) = Self::section_for_read(chunk, section_index) else {
1122 Self::record_ore_read_time(ore_profile, started_at);
1123 return air;
1124 };
1125
1126 Self::with_ore_profile_ref(ore_profile, |profile| {
1127 profile.record_section_read_attempt(chunk_x, chunk_z, section_index);
1128 });
1129 let section_guard = if let Some(profile) = ore_profile {
1130 if let Some(guard) = section.try_read() {
1131 guard
1132 } else {
1133 if let Ok(mut profile) = profile.try_borrow_mut() {
1134 profile.record_section_read_contention();
1135 }
1136 let wait_started_at = Instant::now();
1137 let guard = section.read();
1138 if let Ok(mut profile) = profile.try_borrow_mut() {
1139 profile.record_read_contention_wait_time(wait_started_at.elapsed());
1140 }
1141 guard
1142 }
1143 } else {
1144 section.read()
1145 };
1146 if section_guard.states.has_only_air() {
1147 Self::record_ore_read_time(ore_profile, started_at);
1148 return air;
1149 }
1150
1151 let state = section_guard.states.get(
1152 Self::local_coord(pos.x()),
1153 Self::local_coord(pos.y()),
1154 Self::local_coord(pos.z()),
1155 );
1156 Self::record_ore_read_time(ore_profile, started_at);
1157 state
1158 }
1159
1160 fn section_for_read<'chunk>(
1165 chunk: &'chunk CachedWorldGenChunk<'_>,
1166 section_index: usize,
1167 ) -> Option<&'chunk SectionHolder> {
1168 if !chunk.access_mode.allows_writes() {
1169 return None;
1170 }
1171 chunk.chunk.sections().sections.get(section_index)
1172 }
1173
1174 #[must_use]
1181 pub(crate) fn set_block_state(&mut self, pos: BlockPos, state: BlockStateId) -> bool {
1182 let ore_profile = self.ore_profile;
1183 let started_at = ore_profile.map(|_| Instant::now());
1184 let Some(key) = self.writable_section_key(pos) else {
1185 Self::record_ore_write_time(ore_profile, started_at);
1186 return false;
1187 };
1188
1189 let chunk = self.chunk(key.chunk_x, key.chunk_z, key.status);
1190 if !chunk.access_mode.allows_writes() {
1191 Self::record_ore_write_time(ore_profile, started_at);
1192 return true;
1193 }
1194 let Some(section) = chunk.chunk.sections().sections.get(key.section_index) else {
1195 panic!(
1196 "Worldgen bulk section write at ({}, {}, {}) resolved missing section index {}",
1197 pos.x(),
1198 pos.y(),
1199 pos.z(),
1200 key.section_index
1201 );
1202 };
1203
1204 let mut section_guard = Self::ore_section_write_guard(ore_profile, section, key);
1205 let old_state = Self::set_bulk_block_state(
1206 chunk.holder,
1207 &mut section_guard,
1208 Self::local_coord(pos.x()),
1209 Self::local_coord(pos.y()),
1210 Self::local_coord(pos.z()),
1211 state,
1212 );
1213 Self::with_ore_profile_ref(ore_profile, OreFeatureStats::record_write);
1214 if old_state != state {
1215 chunk.chunk.mark_dirty();
1216 }
1217
1218 Self::record_ore_write_time(ore_profile, started_at);
1219 true
1220 }
1221
1222 fn writable_section_key(&self, pos: BlockPos) -> Option<WritableSectionKey> {
1223 let (chunk_x, chunk_z, status) = self
1224 .region
1225 .writable_chunk_for_pos(pos, "bulk write block")?;
1226 let section_index =
1227 Self::section_index(self.region.min_y(), self.region.height(), pos.y())?;
1228 Some(WritableSectionKey {
1229 chunk_x,
1230 chunk_z,
1231 status,
1232 section_index,
1233 })
1234 }
1235
1236 fn set_bulk_block_state(
1237 holder: &ChunkHolder,
1238 section: &mut ChunkSection,
1239 local_x: usize,
1240 local_y: usize,
1241 local_z: usize,
1242 state: BlockStateId,
1243 ) -> BlockStateId {
1244 let Some(status) = holder.published_status() else {
1245 panic!("worldgen chunk data cannot lose its published status");
1246 };
1247 if status < ChunkStatus::InitializeLight {
1248 return section.set_block_state_for_generation(local_x, local_y, local_z, state);
1249 }
1250
1251 section.finalize_generation_counts_if_needed();
1252 section.set_block_state(local_x, local_y, local_z, state)
1253 }
1254
1255 fn ore_section_write_guard<'section>(
1256 ore_profile: Option<&RefCell<OreFeatureStats>>,
1257 section: &'section SectionHolder,
1258 key: WritableSectionKey,
1259 ) -> SectionWriteGuard<'section> {
1260 Self::with_ore_profile_ref(ore_profile, |profile| {
1261 profile.record_section_write_attempt(key.chunk_x, key.chunk_z, key.section_index);
1262 });
1263 if let Some(profile) = ore_profile {
1264 if let Some(guard) = section.try_write() {
1265 guard
1266 } else {
1267 if let Ok(mut profile) = profile.try_borrow_mut() {
1268 profile.record_section_write_contention();
1269 }
1270 let wait_started_at = Instant::now();
1271 let guard = section.write();
1272 if let Ok(mut profile) = profile.try_borrow_mut() {
1273 profile.record_write_contention_wait_time(wait_started_at.elapsed());
1274 }
1275 guard
1276 }
1277 } else {
1278 section.write()
1279 }
1280 }
1281
1282 #[must_use]
1284 pub(crate) const fn can_write_to_pos(&self, pos: BlockPos) -> bool {
1285 self.region.can_write_to_chunk(
1286 SectionPos::block_to_section_coord(pos.x()),
1287 SectionPos::block_to_section_coord(pos.z()),
1288 )
1289 }
1290
1291 fn chunk(
1292 &mut self,
1293 chunk_x: i32,
1294 chunk_z: i32,
1295 status: ChunkStatus,
1296 ) -> &CachedWorldGenChunk<'region> {
1297 let Some(cache_index) = self.chunk_cache_index(chunk_x, chunk_z) else {
1298 panic!(
1299 "Worldgen bulk section requested chunk ({chunk_x}, {chunk_z}) outside the region cache centered on ({}, {})",
1300 self.region.center.0.x, self.region.center.0.y
1301 );
1302 };
1303
1304 let cache_needs_insert = self.chunks.get(cache_index).is_none_or(Option::is_none);
1305 if cache_needs_insert {
1306 self.with_ore_profile(OreFeatureStats::record_chunk_cache_miss);
1307 let chunk = self.region.chunk(chunk_x, chunk_z, status);
1308 let Some(slot) = self.chunks.get_mut(cache_index) else {
1309 panic!("Worldgen bulk section cache index {cache_index} escaped its storage");
1310 };
1311 *slot = Some(CachedWorldGenChunk {
1312 access_mode: chunk.access_mode,
1313 holder: chunk.holder,
1314 chunk: chunk.chunk,
1315 verified_status: status,
1316 });
1317 } else if self.chunks.get(cache_index).is_some_and(|cached| {
1318 cached
1319 .as_ref()
1320 .is_some_and(|cached| status > cached.verified_status)
1321 }) {
1322 self.with_ore_profile(OreFeatureStats::record_chunk_status_upgrade);
1323 let _ = self.region.chunk(chunk_x, chunk_z, status);
1324 let Some(Some(cached)) = self.chunks.get_mut(cache_index) else {
1325 panic!("Worldgen bulk section cache lost verified chunk ({chunk_x}, {chunk_z})");
1326 };
1327 cached.verified_status = status;
1328 }
1329
1330 let Some(Some(cached)) = self.chunks.get(cache_index) else {
1331 panic!("Worldgen bulk section cache failed to store chunk ({chunk_x}, {chunk_z})");
1332 };
1333 cached
1334 }
1335
1336 fn chunk_cache_index(&self, chunk_x: i32, chunk_z: i32) -> Option<usize> {
1337 let radius = self.chunk_cache_radius;
1338 let size = radius.checked_mul(2)?.checked_add(1)?;
1339 let rel_x = chunk_x
1340 .checked_sub(self.region.center.0.x)?
1341 .checked_add(radius)?;
1342 let rel_z = chunk_z
1343 .checked_sub(self.region.center.0.y)?
1344 .checked_add(radius)?;
1345 if rel_x < 0 || rel_x >= size || rel_z < 0 || rel_z >= size {
1346 return None;
1347 }
1348
1349 usize::try_from(rel_z.checked_mul(size)?.checked_add(rel_x)?).ok()
1350 }
1351
1352 fn section_index(min_y: i32, height: i32, y: i32) -> Option<usize> {
1353 if y < min_y || y >= min_y + height {
1354 return None;
1355 }
1356
1357 usize::try_from((y - min_y) / 16).ok()
1358 }
1359
1360 const fn local_coord(coord: i32) -> usize {
1361 (coord & 15) as usize
1362 }
1363
1364 fn record_ore_read_time(
1365 profile: Option<&RefCell<OreFeatureStats>>,
1366 started_at: Option<Instant>,
1367 ) {
1368 if let Some(started_at) = started_at {
1369 Self::with_ore_profile_ref(profile, |profile| {
1370 profile.record_read_time(started_at.elapsed());
1371 });
1372 }
1373 }
1374
1375 fn record_ore_write_time(
1376 profile: Option<&RefCell<OreFeatureStats>>,
1377 started_at: Option<Instant>,
1378 ) {
1379 if let Some(started_at) = started_at {
1380 Self::with_ore_profile_ref(profile, |profile| {
1381 profile.record_write_time(started_at.elapsed());
1382 });
1383 }
1384 }
1385
1386 fn with_ore_profile(&self, f: impl FnOnce(&mut OreFeatureStats)) {
1387 let Some(profile) = self.ore_profile else {
1388 return;
1389 };
1390 if let Ok(mut profile) = profile.try_borrow_mut() {
1391 f(&mut profile);
1392 }
1393 }
1394
1395 fn with_ore_profile_ref(
1396 profile: Option<&RefCell<OreFeatureStats>>,
1397 f: impl FnOnce(&mut OreFeatureStats),
1398 ) {
1399 let Some(profile) = profile else {
1400 return;
1401 };
1402 if let Ok(mut profile) = profile.try_borrow_mut() {
1403 f(&mut profile);
1404 }
1405 }
1406}
1407
1408const fn abs_diff(left: i32, right: i32) -> i32 {
1409 if left >= right {
1410 left - right
1411 } else {
1412 right - left
1413 }
1414}
1415
1416impl LevelReader for WorldGenRegion<'_> {
1417 fn get_block_state(&self, pos: BlockPos) -> BlockStateId {
1418 self.block_state(pos)
1419 }
1420
1421 fn get_block_entity(&self, pos: BlockPos) -> Option<SharedBlockEntity> {
1422 self.block_entity(pos)
1423 }
1424
1425 fn is_face_sturdy_for(
1426 &self,
1427 state: BlockStateId,
1428 pos: BlockPos,
1429 direction: Direction,
1430 support_type: SupportType,
1431 ) -> bool {
1432 BLOCK_BEHAVIORS
1433 .get_behavior(state.get_block())
1434 .is_face_sturdy(state, self, pos, direction, support_type)
1435 }
1436
1437 fn raw_brightness(&self, pos: BlockPos, sky_darkening: u8) -> u8 {
1438 let sky_light = if self.context.world().dimension_type.has_skylight {
1439 15_u8.saturating_sub(sky_darkening)
1440 } else {
1441 0
1442 };
1443
1444 sky_light.max(self.block_light_at(pos))
1445 }
1446
1447 fn can_see_sky(&self, pos: BlockPos) -> bool {
1448 if !self.context.world().dimension_type.has_skylight {
1449 return false;
1450 }
1451
1452 self.height_at(HeightmapType::MotionBlocking, pos.x(), pos.z()) <= pos.y()
1453 }
1454
1455 fn ambient_light(&self) -> f32 {
1456 self.context.world().dimension_type.ambient_light
1457 }
1458
1459 fn min_y(&self) -> i32 {
1460 WorldGenRegion::min_y(self)
1461 }
1462
1463 fn height(&self) -> i32 {
1464 WorldGenRegion::height(self)
1465 }
1466}
1467
1468impl ScheduledTickAccess for WorldGenRegion<'_> {
1469 fn fluid_tick_delay(&self, fluid: FluidRef) -> i32 {
1470 FLUID_BEHAVIORS
1471 .get_behavior(fluid)
1472 .tick_delay(&self.context.world())
1473 }
1474
1475 fn schedule_block_tick_default(&self, pos: BlockPos, block: BlockRef, delay: i32) -> bool {
1476 WorldGenRegion::schedule_block_tick_default(self, pos, block, delay)
1477 }
1478
1479 fn schedule_fluid_tick_default(&self, pos: BlockPos, fluid: FluidRef, delay: i32) -> bool {
1480 WorldGenRegion::schedule_fluid_tick_default(self, pos, fluid, delay)
1481 }
1482}
1483
1484impl LevelAccessor for WorldGenRegion<'_> {
1485 fn set_block_state(&self, pos: BlockPos, state: BlockStateId, flags: UpdateFlags) -> bool {
1486 WorldGenRegion::set_block_state(self, pos, state, flags)
1487 }
1488}
1489
1490#[cfg(test)]
1491mod tests {
1492 use std::sync::{Arc, Weak};
1493
1494 use steel_registry::{init_vanilla_registry, vanilla_blocks};
1495 use steel_utils::{BlockPos, ChunkPos, types::UpdateFlags};
1496
1497 use crate::behavior::init_behaviors;
1498 use crate::chunk::{
1499 Chunk,
1500 chunk_holder::ChunkHolder,
1501 chunk_ticket_manager::ChunkTicketLevel,
1502 heightmap::{Heightmap, HeightmapType},
1503 section::{ChunkSection, Sections},
1504 status::ChunkStatus,
1505 };
1506
1507 use super::{
1508 CachedWorldGenChunk, WorldGenAccessMode, WorldGenBulkSectionAccess, WorldGenChunkRef,
1509 WorldGenRegion,
1510 };
1511
1512 fn test_holder() -> ChunkHolder {
1513 ChunkHolder::new(
1514 ChunkPos::new(0, 0),
1515 ChunkTicketLevel::FULL_CHUNK,
1516 Some(ChunkTicketLevel::FULL_CHUNK),
1517 0,
1518 16,
1519 )
1520 }
1521
1522 fn published_holder(status: ChunkStatus) -> Arc<ChunkHolder> {
1523 let holder = Arc::new(test_holder());
1524 holder.insert_chunk(
1525 Chunk::new(
1526 Sections::from_owned(vec![ChunkSection::new_empty()].into_boxed_slice()),
1527 ChunkPos::new(0, 0),
1528 0,
1529 16,
1530 Weak::new(),
1531 ),
1532 status,
1533 );
1534 holder
1535 }
1536
1537 #[test]
1538 fn full_imposter_maps_worldgen_height_queries_to_final_heightmaps() {
1539 init_vanilla_registry();
1540 let chunk = Chunk::new(
1541 Sections::from_owned(vec![ChunkSection::new_empty()].into_boxed_slice()),
1542 ChunkPos::new(0, 0),
1543 0,
1544 16,
1545 Weak::new(),
1546 );
1547 let mut worldgen = Heightmap::new(HeightmapType::WorldSurfaceWg, 0, 16);
1548 worldgen.set_height(3, 7, 4);
1549 let mut final_map = Heightmap::new(HeightmapType::WorldSurface, 0, 16);
1550 final_map.set_height(3, 7, 11);
1551 {
1552 let mut heightmaps = chunk.heightmaps.write();
1553 heightmaps.replace(worldgen);
1554 heightmaps.replace(final_map);
1555 }
1556 let _ = chunk.promote_to_full();
1557 let holder = test_holder();
1558 let view = WorldGenChunkRef {
1559 holder: &holder,
1560 chunk: &chunk,
1561 access_mode: WorldGenAccessMode::ReadOnlyFull,
1562 };
1563
1564 assert_eq!(
1565 view.full_imposter_height_at(HeightmapType::WorldSurfaceWg, 3, 7),
1566 11
1567 );
1568 }
1569
1570 #[test]
1571 fn chessboard_distance_matches_chunk_dependency_radius() {
1572 let center = ChunkPos::new(4, -2);
1573
1574 assert_eq!(WorldGenRegion::chessboard_distance(center, 4, -2), 0);
1575 assert_eq!(WorldGenRegion::chessboard_distance(center, 5, -3), 1);
1576 assert_eq!(WorldGenRegion::chessboard_distance(center, -4, 6), 8);
1577 }
1578
1579 #[test]
1580 fn biome_quart_y_indices_clamp_to_vertical_biome_range() {
1581 assert_eq!(WorldGenRegion::biome_quart_y_indices(-64, 24, -17), (0, 0));
1582 assert_eq!(WorldGenRegion::biome_quart_y_indices(-64, 24, -16), (0, 0));
1583 assert_eq!(WorldGenRegion::biome_quart_y_indices(-64, 24, -13), (0, 3));
1584 assert_eq!(WorldGenRegion::biome_quart_y_indices(-64, 24, -12), (1, 0));
1585 assert_eq!(WorldGenRegion::biome_quart_y_indices(-64, 24, 79), (23, 3));
1586 assert_eq!(WorldGenRegion::biome_quart_y_indices(-64, 24, 80), (23, 3));
1587 assert_eq!(WorldGenRegion::biome_quart_y_indices(-64, 24, 81), (23, 3));
1588 }
1589
1590 #[test]
1591 fn bulk_section_index_matches_world_height_bounds() {
1592 assert_eq!(
1593 WorldGenBulkSectionAccess::section_index(-64, 384, -65),
1594 None
1595 );
1596 assert_eq!(
1597 WorldGenBulkSectionAccess::section_index(-64, 384, -64),
1598 Some(0)
1599 );
1600 assert_eq!(
1601 WorldGenBulkSectionAccess::section_index(-64, 384, -49),
1602 Some(0)
1603 );
1604 assert_eq!(
1605 WorldGenBulkSectionAccess::section_index(-64, 384, -48),
1606 Some(1)
1607 );
1608 assert_eq!(
1609 WorldGenBulkSectionAccess::section_index(-64, 384, 319),
1610 Some(23)
1611 );
1612 assert_eq!(
1613 WorldGenBulkSectionAccess::section_index(-64, 384, 320),
1614 None
1615 );
1616 }
1617
1618 #[test]
1619 fn bulk_section_local_coord_uses_vanilla_section_mask() {
1620 assert_eq!(WorldGenBulkSectionAccess::local_coord(-17), 15);
1621 assert_eq!(WorldGenBulkSectionAccess::local_coord(-16), 0);
1622 assert_eq!(WorldGenBulkSectionAccess::local_coord(-1), 15);
1623 assert_eq!(WorldGenBulkSectionAccess::local_coord(0), 0);
1624 assert_eq!(WorldGenBulkSectionAccess::local_coord(31), 15);
1625 }
1626
1627 #[test]
1628 fn bulk_section_reads_use_empty_proto_sections_for_full_imposters() {
1629 init_vanilla_registry();
1630 let chunk = Chunk::new(
1631 Sections::from_owned(vec![ChunkSection::new_empty()].into_boxed_slice()),
1632 ChunkPos::new(0, 0),
1633 0,
1634 16,
1635 Weak::new(),
1636 );
1637 let holder = test_holder();
1638
1639 let writable = CachedWorldGenChunk {
1640 holder: &holder,
1641 chunk: &chunk,
1642 verified_status: ChunkStatus::Empty,
1643 access_mode: WorldGenAccessMode::WritableProto,
1644 };
1645 let full_imposter = CachedWorldGenChunk {
1646 holder: &holder,
1647 chunk: &chunk,
1648 verified_status: ChunkStatus::Full,
1649 access_mode: WorldGenAccessMode::ReadOnlyFull,
1650 };
1651
1652 assert!(WorldGenBulkSectionAccess::section_for_read(&writable, 0).is_some());
1653 assert!(WorldGenBulkSectionAccess::section_for_read(&full_imposter, 0).is_none());
1654 }
1655
1656 #[test]
1657 fn bulk_write_defers_counts_only_for_pre_light_proto_chunks() {
1658 init_vanilla_registry();
1659 init_behaviors();
1660 let stone = vanilla_blocks::STONE.default_state();
1661 let air = vanilla_blocks::AIR.default_state();
1662
1663 let mut pre_light_section = ChunkSection::new_empty();
1664 let pre_light_holder = published_holder(ChunkStatus::Empty);
1665
1666 WorldGenBulkSectionAccess::set_bulk_block_state(
1667 &pre_light_holder,
1668 &mut pre_light_section,
1669 1,
1670 2,
1671 3,
1672 stone,
1673 );
1674
1675 assert_eq!(pre_light_section.non_empty_block_count(), 0);
1676
1677 let mut initialized_section = ChunkSection::new_empty();
1678 let initialized_holder = published_holder(ChunkStatus::InitializeLight);
1679
1680 WorldGenBulkSectionAccess::set_bulk_block_state(
1681 &initialized_holder,
1682 &mut initialized_section,
1683 1,
1684 2,
1685 3,
1686 stone,
1687 );
1688
1689 assert_eq!(initialized_section.non_empty_block_count(), 1);
1690
1691 let mut initialized_building_section = ChunkSection::new_empty();
1692 initialized_building_section.set_block_state_for_generation(1, 2, 3, stone);
1693
1694 let old_state = WorldGenBulkSectionAccess::set_bulk_block_state(
1695 &initialized_holder,
1696 &mut initialized_building_section,
1697 4,
1698 5,
1699 6,
1700 stone,
1701 );
1702
1703 assert_eq!(old_state, air);
1704 assert_eq!(initialized_building_section.non_empty_block_count(), 2);
1705 }
1706
1707 #[test]
1708 fn cached_worldgen_view_observes_holder_status_advancement() {
1709 init_vanilla_registry();
1710 init_behaviors();
1711 let holder = published_holder(ChunkStatus::Features);
1712 let Some(chunk) = holder.try_chunk(ChunkStatus::Features) else {
1713 panic!("test chunk was not published");
1714 };
1715 let view = WorldGenChunkRef {
1716 holder: &holder,
1717 chunk,
1718 access_mode: WorldGenAccessMode::WritableProto,
1719 };
1720
1721 holder.finish_generation_status_for_test(ChunkStatus::InitializeLight);
1722
1723 let stone = vanilla_blocks::STONE.default_state();
1724 let old_state = view.chunk.set_block_state_for_generation(
1725 view.published_status(),
1726 BlockPos::new(1, 2, 3),
1727 stone,
1728 UpdateFlags::UPDATE_NONE,
1729 );
1730 assert!(old_state.is_some());
1731 assert_eq!(
1732 chunk.sections().sections[0].read().non_empty_block_count(),
1733 1
1734 );
1735
1736 let mut section = chunk.sections().sections[0].write();
1737 WorldGenBulkSectionAccess::set_bulk_block_state(&holder, &mut section, 4, 5, 6, stone);
1738 assert_eq!(section.non_empty_block_count(), 2);
1739 }
1740}