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