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;
9
10pub enum TerrainPhase {}
12
13#[repr(transparent)]
29pub struct GenerationChunk<'a, Phase> {
30 chunk: &'a Chunk,
31 phase: PhantomData<fn() -> Phase>,
32}
33
34impl<Phase> Copy for GenerationChunk<'_, Phase> {}
35
36impl<Phase> Clone for GenerationChunk<'_, Phase> {
37 fn clone(&self) -> Self {
38 *self
39 }
40}
41
42impl<'a, Phase> GenerationChunk<'a, Phase> {
43 const fn from_chunk(chunk: &'a Chunk) -> Self {
44 Self {
45 chunk,
46 phase: PhantomData,
47 }
48 }
49
50 #[cfg(test)]
51 pub(crate) const fn for_test(chunk: &'a Chunk) -> Self {
52 Self::from_chunk(chunk)
53 }
54
55 fn acquire_input(holder: &'a ChunkHolder, input_status: ChunkStatus) -> Self {
56 assert_eq!(
57 holder.published_status(),
58 Some(input_status),
59 "generation capability requires the exact published input status"
60 );
61 let Some(chunk) = holder.try_chunk(input_status) else {
62 panic!("chunk data missing after {input_status:?} publication");
63 };
64 Self::from_chunk(chunk)
65 }
66
67 #[must_use]
69 pub const fn pos(self) -> ChunkPos {
70 self.chunk.pos()
71 }
72
73 #[must_use]
75 pub const fn min_y(self) -> i32 {
76 self.chunk.min_y()
77 }
78
79 #[must_use]
81 pub const fn height(self) -> i32 {
82 self.chunk.height()
83 }
84
85 #[must_use]
87 #[inline]
88 pub fn section_count(self) -> usize {
89 self.chunk.sections().sections.len()
90 }
91
92 #[must_use]
94 #[inline]
95 pub fn get_relative_block(
96 self,
97 relative_x: usize,
98 relative_y: usize,
99 relative_z: usize,
100 ) -> Option<BlockStateId> {
101 self.chunk
102 .get_relative_block(relative_x, relative_y, relative_z)
103 }
104
105 #[must_use]
107 #[inline]
108 pub fn get_block_state(self, pos: BlockPos) -> BlockStateId {
109 self.chunk.get_block_state(pos)
110 }
111}
112
113impl GenerationChunk<'_, TerrainPhase> {
114 pub(crate) fn acquire(holder: &ChunkHolder) -> GenerationChunk<'_, TerrainPhase> {
115 GenerationChunk::acquire_input(holder, ChunkStatus::Biomes)
116 }
117
118 #[inline]
120 pub fn write_block_batch(self, blocks: &[(usize, usize, usize, BlockStateId)]) {
121 self.chunk
122 .write_block_batch_for_generation(ChunkStatus::Biomes, blocks);
123 }
124
125 #[inline]
127 pub fn write_noise_column(self, x: usize, z: usize, blocks: &[(usize, BlockStateId)]) {
128 self.chunk
129 .write_column_blocks_for_generation(ChunkStatus::Biomes, x, z, blocks);
130 }
131
132 pub fn replace_noise_heightmaps(self, ocean_floor: Heightmap, world_surface: Heightmap) {
138 assert_eq!(ocean_floor.heightmap_type(), HeightmapType::OceanFloorWg);
139 assert_eq!(
140 world_surface.heightmap_type(),
141 HeightmapType::WorldSurfaceWg
142 );
143 let mut heightmaps = self.chunk.heightmaps.write();
144 heightmaps.replace(ocean_floor);
145 heightmaps.replace(world_surface);
146 }
147
148 pub fn install_post_noise_state<T>(self, state: T)
150 where
151 T: DowncastType + Send + Sync,
152 {
153 self.chunk.install_transient_generation_state(state);
154 }
155
156 #[inline]
158 pub fn mark_pos_for_postprocessing(self, pos: BlockPos) {
159 self.chunk.mark_pos_for_postprocessing(pos);
160 }
161
162 #[inline]
164 pub fn set_relative_block(
165 self,
166 relative_x: usize,
167 relative_y: usize,
168 relative_z: usize,
169 state: BlockStateId,
170 ) {
171 self.chunk.set_relative_block_for_generation(
172 ChunkStatus::Biomes,
173 relative_x,
174 relative_y,
175 relative_z,
176 state,
177 );
178 }
179 pub fn prime_world_surface_heightmap(self) {
181 self.chunk
182 .prime_heightmaps(&[HeightmapType::WorldSurfaceWg]);
183 }
184
185 pub fn with_post_noise_state_mut<T, R>(self, f: impl FnOnce(&mut T) -> R) -> Option<R>
187 where
188 T: DowncastType + Send + Sync,
189 {
190 self.chunk.with_transient_generation_state_mut(f)
191 }
192
193 #[must_use]
195 pub fn read_all_biomes(self) -> Box<[u16]> {
196 self.chunk.sections().read_all_biomes()
197 }
198
199 #[inline]
201 pub fn read_column_into(self, local_x: usize, local_z: usize, output: &mut Vec<BlockStateId>) {
202 self.chunk
203 .sections()
204 .read_column_into(local_x, local_z, output);
205 }
206
207 #[must_use]
209 #[inline]
210 pub fn world_surface_height_at(self, local_x: usize, local_z: usize) -> i32 {
211 self.chunk
212 .generation_height_at(HeightmapType::WorldSurfaceWg, local_x, local_z)
213 }
214
215 #[inline]
217 pub fn write_column(self, local_x: usize, local_z: usize, blocks: &[(usize, BlockStateId)]) {
218 self.chunk.write_column_blocks_for_generation(
219 ChunkStatus::Biomes,
220 local_x,
221 local_z,
222 blocks,
223 );
224 self.chunk.update_heightmaps_after_direct_column_writes(
225 ChunkStatus::Biomes,
226 local_x,
227 local_z,
228 blocks,
229 );
230 self.chunk.mark_dirty();
231 }
232
233 pub fn clear_post_noise_state(self) {
235 self.chunk.clear_transient_generation_state();
236 }
237
238 pub fn consume_post_noise_state<T, R>(self, f: impl FnOnce(Option<&mut T>) -> R) -> R
240 where
241 T: DowncastType + Send + Sync,
242 {
243 self.chunk.consume_transient_generation_state(f)
244 }
245
246 #[inline]
248 pub fn set_block_state(self, pos: BlockPos, state: BlockStateId) {
249 let _ = self.chunk.set_block_state_for_generation(
250 ChunkStatus::Biomes,
251 pos,
252 state,
253 UpdateFlags::empty(),
254 );
255 }
256
257 pub fn with_world_surface_heightmap<R>(self, f: impl FnOnce(&Heightmap) -> R) -> Option<R> {
259 let heightmaps = self.chunk.generation_heightmaps();
260 if let Some(heightmap) = heightmaps.get(HeightmapType::WorldSurfaceWg) {
261 return Some(f(heightmap));
262 }
263 drop(heightmaps);
264
265 self.prime_world_surface_heightmap();
266 let heightmaps = self.chunk.generation_heightmaps();
267 heightmaps.get(HeightmapType::WorldSurfaceWg).map(f)
268 }
269}
270
271#[cfg(test)]
272mod tests {
273 use std::sync::{Arc, Weak};
274
275 use steel_registry::init_vanilla_registry;
276 use steel_utils::ChunkPos;
277
278 use super::{GenerationChunk, TerrainPhase};
279 use crate::chunk::Chunk;
280 use crate::chunk::chunk_holder::ChunkHolder;
281 use crate::chunk::chunk_ticket_manager::ChunkTicketLevel;
282 use crate::chunk::section::{ChunkSection, Sections};
283 use crate::chunk::status::ChunkStatus;
284
285 fn holder_at(status: ChunkStatus) -> Arc<ChunkHolder> {
286 init_vanilla_registry();
287 let pos = ChunkPos::new(0, 0);
288 let holder = Arc::new(ChunkHolder::new(
289 pos,
290 ChunkTicketLevel::STRONGEST,
291 None,
292 0,
293 16,
294 ));
295 let chunk = Chunk::new(
296 Sections::from_owned(vec![ChunkSection::new_empty()].into_boxed_slice()),
297 pos,
298 0,
299 16,
300 Weak::new(),
301 );
302 holder.insert_chunk(chunk, status);
303 holder
304 }
305
306 #[test]
307 #[should_panic(expected = "generation capability requires the exact published input status")]
308 fn capability_rejects_the_wrong_published_input() {
309 let holder = holder_at(ChunkStatus::StructureReferences);
310 let _ = GenerationChunk::<TerrainPhase>::acquire(&holder);
311 }
312
313 #[test]
314 #[should_panic(expected = "generation capability requires the exact published input status")]
315 fn capability_rejects_an_advanced_published_input() {
316 let holder = holder_at(ChunkStatus::Terrain);
317 let _ = GenerationChunk::<TerrainPhase>::acquire(&holder);
318 }
319}
320
321#[cfg(feature = "benchmark-support")]
322pub mod benchmark_support {
324 use glam::IVec3;
325 use steel_worldgen::noise::Beardifier;
326
327 use super::{GenerationChunk, TerrainPhase};
328 use crate::chunk::Chunk;
329 use crate::worldgen::generator::ChunkGenerator;
330
331 pub fn fill_from_noise<G>(generator: &G, chunk: &Chunk, beardifier: Option<&Beardifier>)
333 where
334 G: ChunkGenerator + ?Sized,
335 {
336 generator.fill_from_noise(
337 GenerationChunk::<TerrainPhase>::from_chunk(chunk),
338 beardifier,
339 );
340 }
341
342 pub fn build_surface<G>(generator: &G, chunk: &Chunk, neighbor_biomes: &dyn Fn(IVec3) -> u16)
344 where
345 G: ChunkGenerator + ?Sized,
346 {
347 generator.build_surface(
348 GenerationChunk::<TerrainPhase>::from_chunk(chunk),
349 neighbor_biomes,
350 );
351 }
352
353 pub fn apply_carvers<G>(generator: &G, chunk: &Chunk)
355 where
356 G: ChunkGenerator + ?Sized,
357 {
358 generator.apply_carvers(GenerationChunk::<TerrainPhase>::from_chunk(chunk));
359 }
360}