Skip to main content

steel_core/worldgen/generator/
generation_chunk.rs

1use std::marker::PhantomData;
2
3use steel_utils::{BlockPos, BlockStateId, ChunkPos, DowncastType, types::UpdateFlags};
4
5use crate::chunk::Chunk;
6use crate::chunk::chunk_holder::ChunkHolder;
7use crate::chunk::heightmap::{Heightmap, HeightmapType};
8use crate::chunk::status::ChunkStatus;
9use crate::worldgen::carving_mask::CarvingMask;
10
11/// Marker for the Noise generation operation.
12pub enum NoisePhase {}
13
14/// Marker for the Surface generation operation.
15pub enum SurfacePhase {}
16
17/// Marker for the Carvers generation operation.
18pub enum CarversPhase {}
19
20/// Stage-scoped access to the center chunk being generated.
21///
22/// The scheduler constructs this capability only after the chunk's input status is
23/// published. It deliberately does not expose the underlying [`Chunk`] or its raw
24/// section storage, so status-sensitive writes stay tied to the operation that owns
25/// them.
26///
27/// ```compile_fail
28/// use steel_core::worldgen::generator::{GenerationChunk, NoisePhase};
29///
30/// fn write_surface_column_during_noise(chunk: GenerationChunk<'_, NoisePhase>) {
31///     chunk.write_column(0, 0, &[]);
32/// }
33/// ```
34#[repr(transparent)]
35pub struct GenerationChunk<'a, Phase> {
36    chunk: &'a Chunk,
37    phase: PhantomData<fn() -> Phase>,
38}
39
40impl<Phase> Copy for GenerationChunk<'_, Phase> {}
41
42impl<Phase> Clone for GenerationChunk<'_, Phase> {
43    fn clone(&self) -> Self {
44        *self
45    }
46}
47
48impl<'a, Phase> GenerationChunk<'a, Phase> {
49    const fn from_chunk(chunk: &'a Chunk) -> Self {
50        Self {
51            chunk,
52            phase: PhantomData,
53        }
54    }
55
56    #[cfg(test)]
57    pub(crate) const fn for_test(chunk: &'a Chunk) -> Self {
58        Self::from_chunk(chunk)
59    }
60
61    fn acquire_input(holder: &'a ChunkHolder, input_status: ChunkStatus) -> Self {
62        assert_eq!(
63            holder.published_status(),
64            Some(input_status),
65            "generation capability requires the exact published input status"
66        );
67        let Some(chunk) = holder.try_chunk(input_status) else {
68            panic!("chunk data missing after {input_status:?} publication");
69        };
70        Self::from_chunk(chunk)
71    }
72
73    /// Returns the chunk position.
74    #[must_use]
75    pub const fn pos(self) -> ChunkPos {
76        self.chunk.pos()
77    }
78
79    /// Returns the dimension's minimum block Y.
80    #[must_use]
81    pub const fn min_y(self) -> i32 {
82        self.chunk.min_y()
83    }
84
85    /// Returns the dimension's build height.
86    #[must_use]
87    pub const fn height(self) -> i32 {
88        self.chunk.height()
89    }
90
91    /// Returns the number of vertical chunk sections.
92    #[must_use]
93    #[inline]
94    pub fn section_count(self) -> usize {
95        self.chunk.sections().sections.len()
96    }
97
98    /// Reads one block using coordinates relative to the dimension's minimum Y.
99    #[must_use]
100    #[inline]
101    pub fn get_relative_block(
102        self,
103        relative_x: usize,
104        relative_y: usize,
105        relative_z: usize,
106    ) -> Option<BlockStateId> {
107        self.chunk
108            .get_relative_block(relative_x, relative_y, relative_z)
109    }
110
111    /// Reads one block at a world position.
112    #[must_use]
113    #[inline]
114    pub fn get_block_state(self, pos: BlockPos) -> BlockStateId {
115        self.chunk.get_block_state(pos)
116    }
117}
118
119impl GenerationChunk<'_, NoisePhase> {
120    pub(crate) fn acquire(holder: &ChunkHolder) -> GenerationChunk<'_, NoisePhase> {
121        GenerationChunk::acquire_input(holder, ChunkStatus::Biomes)
122    }
123
124    /// Writes a Noise-stage block batch using the published Biomes semantics.
125    #[inline]
126    pub fn write_block_batch(self, blocks: &[(usize, usize, usize, BlockStateId)]) {
127        self.chunk
128            .write_block_batch_for_generation(ChunkStatus::Biomes, blocks);
129    }
130
131    /// Replaces the two heightmaps initialized directly by terrain noise filling.
132    ///
133    /// # Panics
134    ///
135    /// Panics if either heightmap has a type other than its corresponding noise-stage type.
136    pub fn replace_noise_heightmaps(self, ocean_floor: Heightmap, world_surface: Heightmap) {
137        assert_eq!(ocean_floor.heightmap_type(), HeightmapType::OceanFloorWg);
138        assert_eq!(
139            world_surface.heightmap_type(),
140            HeightmapType::WorldSurfaceWg
141        );
142        let mut heightmaps = self.chunk.heightmaps.write();
143        heightmaps.replace(ocean_floor);
144        heightmaps.replace(world_surface);
145    }
146
147    /// Installs generator-owned state for reuse by Surface and Carvers.
148    pub fn install_post_noise_state<T>(self, state: T)
149    where
150        T: DowncastType + Send + Sync,
151    {
152        self.chunk.install_transient_generation_state(state);
153    }
154
155    /// Marks a position for Vanilla generation postprocessing.
156    #[inline]
157    pub fn mark_pos_for_postprocessing(self, pos: BlockPos) {
158        self.chunk.mark_pos_for_postprocessing(pos);
159    }
160
161    /// Writes one block using the published Biomes semantics.
162    #[inline]
163    pub fn set_relative_block(
164        self,
165        relative_x: usize,
166        relative_y: usize,
167        relative_z: usize,
168        state: BlockStateId,
169    ) {
170        self.chunk.set_relative_block_for_generation(
171            ChunkStatus::Biomes,
172            relative_x,
173            relative_y,
174            relative_z,
175            state,
176        );
177    }
178}
179
180impl GenerationChunk<'_, SurfacePhase> {
181    pub(crate) fn acquire(holder: &ChunkHolder) -> GenerationChunk<'_, SurfacePhase> {
182        GenerationChunk::acquire_input(holder, ChunkStatus::Noise)
183    }
184
185    /// Ensures the world-surface worldgen heightmap exists.
186    pub fn prime_world_surface_heightmap(self) {
187        self.chunk
188            .prime_heightmaps(&[HeightmapType::WorldSurfaceWg]);
189    }
190
191    /// Borrows generator-owned state retained after Noise.
192    pub fn with_post_noise_state_mut<T, R>(self, f: impl FnOnce(&mut T) -> R) -> Option<R>
193    where
194        T: DowncastType + Send + Sync,
195    {
196        self.chunk.with_transient_generation_state_mut(f)
197    }
198
199    /// Copies every biome palette entry in section order.
200    #[must_use]
201    pub fn read_all_biomes(self) -> Box<[u16]> {
202        self.chunk.sections().read_all_biomes()
203    }
204
205    /// Reads one complete block column into `output`.
206    #[inline]
207    pub fn read_column_into(self, local_x: usize, local_z: usize, output: &mut Vec<BlockStateId>) {
208        self.chunk
209            .sections()
210            .read_column_into(local_x, local_z, output);
211    }
212
213    /// Reads the world-surface worldgen height, lazily priming it if needed.
214    #[must_use]
215    #[inline]
216    pub fn world_surface_height_at(self, local_x: usize, local_z: usize) -> i32 {
217        self.chunk
218            .generation_height_at(HeightmapType::WorldSurfaceWg, local_x, local_z)
219    }
220
221    /// Writes one Surface-stage column and applies its Noise-input heightmap effects.
222    #[inline]
223    pub fn write_column(self, local_x: usize, local_z: usize, blocks: &[(usize, BlockStateId)]) {
224        self.chunk
225            .write_column_blocks_for_generation(ChunkStatus::Noise, local_x, local_z, blocks);
226        self.chunk.update_heightmaps_after_direct_column_writes(
227            ChunkStatus::Noise,
228            local_x,
229            local_z,
230            blocks,
231        );
232        self.chunk.mark_dirty();
233    }
234
235    /// Writes one block using the published Noise semantics.
236    #[inline]
237    pub fn set_relative_block(
238        self,
239        relative_x: usize,
240        relative_y: usize,
241        relative_z: usize,
242        state: BlockStateId,
243    ) {
244        self.chunk.set_relative_block_for_generation(
245            ChunkStatus::Noise,
246            relative_x,
247            relative_y,
248            relative_z,
249            state,
250        );
251    }
252}
253
254impl GenerationChunk<'_, CarversPhase> {
255    pub(crate) fn acquire(holder: &ChunkHolder) -> GenerationChunk<'_, CarversPhase> {
256        GenerationChunk::acquire_input(holder, ChunkStatus::Surface)
257    }
258
259    /// Ensures the world-surface worldgen heightmap exists.
260    pub fn prime_world_surface_heightmap(self) {
261        self.chunk
262            .prime_heightmaps(&[HeightmapType::WorldSurfaceWg]);
263    }
264
265    /// Drops retained generator state without running Carvers.
266    pub fn clear_post_noise_state(self) {
267        self.chunk.clear_transient_generation_state();
268    }
269
270    /// Consumes generator-owned state retained after Noise.
271    pub fn consume_post_noise_state<T, R>(self, f: impl FnOnce(Option<&mut T>) -> R) -> R
272    where
273        T: DowncastType + Send + Sync,
274    {
275        self.chunk.consume_transient_generation_state(f)
276    }
277
278    /// Runs `f` with the chunk's lazily initialized carving mask.
279    pub fn with_carving_mask<R>(self, f: impl FnOnce(&mut CarvingMask) -> R) -> R {
280        let mut mask = self.chunk.get_or_create_carving_mask();
281        f(&mut mask)
282    }
283
284    /// Sets one carved block using the published Surface semantics.
285    #[inline]
286    pub fn set_block_state(self, pos: BlockPos, state: BlockStateId) {
287        let _ = self.chunk.set_block_state_for_generation(
288            ChunkStatus::Surface,
289            pos,
290            state,
291            UpdateFlags::empty(),
292        );
293    }
294
295    /// Runs `f` with the world-surface worldgen heightmap if priming provides it.
296    pub fn with_world_surface_heightmap<R>(self, f: impl FnOnce(&Heightmap) -> R) -> Option<R> {
297        let heightmaps = self.chunk.generation_heightmaps();
298        if let Some(heightmap) = heightmaps.get(HeightmapType::WorldSurfaceWg) {
299            return Some(f(heightmap));
300        }
301        drop(heightmaps);
302
303        self.prime_world_surface_heightmap();
304        let heightmaps = self.chunk.generation_heightmaps();
305        heightmaps.get(HeightmapType::WorldSurfaceWg).map(f)
306    }
307
308    /// Marks a position for Vanilla generation postprocessing.
309    #[inline]
310    pub fn mark_pos_for_postprocessing(self, pos: BlockPos) {
311        self.chunk.mark_pos_for_postprocessing(pos);
312    }
313}
314
315#[cfg(test)]
316mod tests {
317    use std::sync::{Arc, Weak};
318
319    use steel_registry::init_vanilla_registry;
320    use steel_utils::ChunkPos;
321
322    use super::{GenerationChunk, NoisePhase, SurfacePhase};
323    use crate::chunk::Chunk;
324    use crate::chunk::chunk_holder::ChunkHolder;
325    use crate::chunk::chunk_ticket_manager::ChunkTicketLevel;
326    use crate::chunk::section::{ChunkSection, Sections};
327    use crate::chunk::status::ChunkStatus;
328
329    fn holder_at(status: ChunkStatus) -> Arc<ChunkHolder> {
330        init_vanilla_registry();
331        let pos = ChunkPos::new(0, 0);
332        let holder = Arc::new(ChunkHolder::new(
333            pos,
334            ChunkTicketLevel::STRONGEST,
335            None,
336            0,
337            16,
338        ));
339        let chunk = Chunk::new(
340            Sections::from_owned(vec![ChunkSection::new_empty()].into_boxed_slice()),
341            pos,
342            0,
343            16,
344            Weak::new(),
345        );
346        holder.insert_chunk(chunk, status);
347        holder
348    }
349
350    #[test]
351    #[should_panic(expected = "generation capability requires the exact published input status")]
352    fn capability_rejects_the_wrong_published_input() {
353        let holder = holder_at(ChunkStatus::Biomes);
354        let _ = GenerationChunk::<SurfacePhase>::acquire(&holder);
355    }
356
357    #[test]
358    #[should_panic(expected = "generation capability requires the exact published input status")]
359    fn capability_rejects_an_advanced_published_input() {
360        let holder = holder_at(ChunkStatus::Surface);
361        let _ = GenerationChunk::<NoisePhase>::acquire(&holder);
362    }
363}
364
365#[cfg(feature = "benchmark-support")]
366/// Direct generator calls used by Criterion benchmarks.
367pub mod benchmark_support {
368    use glam::IVec3;
369    use steel_worldgen::noise::Beardifier;
370
371    use super::{CarversPhase, GenerationChunk, NoisePhase, SurfacePhase};
372    use crate::chunk::Chunk;
373    use crate::worldgen::generator::ChunkGenerator;
374
375    /// Calls Noise directly for a Criterion benchmark.
376    pub fn fill_from_noise<G>(generator: &G, chunk: &Chunk, beardifier: Option<&Beardifier>)
377    where
378        G: ChunkGenerator + ?Sized,
379    {
380        generator.fill_from_noise(GenerationChunk::<NoisePhase>::from_chunk(chunk), beardifier);
381    }
382
383    /// Calls Surface directly for a Criterion benchmark.
384    pub fn build_surface<G>(generator: &G, chunk: &Chunk, neighbor_biomes: &dyn Fn(IVec3) -> u16)
385    where
386        G: ChunkGenerator + ?Sized,
387    {
388        generator.build_surface(
389            GenerationChunk::<SurfacePhase>::from_chunk(chunk),
390            neighbor_biomes,
391        );
392    }
393
394    /// Calls Carvers directly for a Criterion benchmark.
395    pub fn apply_carvers<G>(generator: &G, chunk: &Chunk)
396    where
397        G: ChunkGenerator + ?Sized,
398    {
399        generator.apply_carvers(GenerationChunk::<CarversPhase>::from_chunk(chunk));
400    }
401}