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