steel_core/worldgen/generator/
generation_chunk.rs1use 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
11pub enum NoisePhase {}
13
14pub enum SurfacePhase {}
16
17pub enum CarversPhase {}
19
20#[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 #[must_use]
75 pub const fn pos(self) -> ChunkPos {
76 self.chunk.pos()
77 }
78
79 #[must_use]
81 pub const fn min_y(self) -> i32 {
82 self.chunk.min_y()
83 }
84
85 #[must_use]
87 pub const fn height(self) -> i32 {
88 self.chunk.height()
89 }
90
91 #[must_use]
93 #[inline]
94 pub fn section_count(self) -> usize {
95 self.chunk.sections().sections.len()
96 }
97
98 #[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 #[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 #[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 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 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 #[inline]
157 pub fn mark_pos_for_postprocessing(self, pos: BlockPos) {
158 self.chunk.mark_pos_for_postprocessing(pos);
159 }
160
161 #[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 pub fn prime_world_surface_heightmap(self) {
187 self.chunk
188 .prime_heightmaps(&[HeightmapType::WorldSurfaceWg]);
189 }
190
191 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 #[must_use]
201 pub fn read_all_biomes(self) -> Box<[u16]> {
202 self.chunk.sections().read_all_biomes()
203 }
204
205 #[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 #[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 #[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 #[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 pub fn prime_world_surface_heightmap(self) {
261 self.chunk
262 .prime_heightmaps(&[HeightmapType::WorldSurfaceWg]);
263 }
264
265 pub fn clear_post_noise_state(self) {
267 self.chunk.clear_transient_generation_state();
268 }
269
270 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 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 #[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 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 #[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")]
366pub 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 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 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 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}