1use std::sync::Arc;
5
6use crate::chunk::{
7 chunk_generation_task::StaticCache2D, chunk_holder::ChunkHolder,
8 chunk_status_tasks::ChunkStatusTasks, status::ChunkStatus,
9};
10use crate::worldgen::context::WorldGenContext;
11
12const STATUS_COUNT: usize = 12;
14const MAX_RADIUS: usize = 16;
16
17#[derive(Debug, Clone, Copy)]
19pub struct ChunkDependencies {
20 dependency_by_radius: [Option<ChunkStatus>; MAX_RADIUS],
21 len: usize,
22 radius_by_dependency: [usize; STATUS_COUNT],
23}
24
25impl ChunkDependencies {
26 pub const EMPTY: Self = Self {
28 dependency_by_radius: [None; MAX_RADIUS],
29 len: 0,
30 radius_by_dependency: [0; STATUS_COUNT],
31 };
32
33 #[must_use]
35 const fn from_requirements(
36 reqs: &[(ChunkStatus, usize)],
37 parent_status: Option<ChunkStatus>,
38 ) -> Self {
39 let mut dependency_by_radius = [None; MAX_RADIUS];
40 let mut len = 0;
41
42 if let Some(parent) = parent_status {
44 dependency_by_radius[0] = Some(parent);
45 len = 1;
46 }
47
48 let mut i = 0;
50 while i < reqs.len() {
51 let (status, radius) = reqs[i];
52 let new_len = radius + 1;
53
54 if new_len > len {
56 let mut j = len;
57 while j < new_len {
58 dependency_by_radius[j] = Some(status);
59 j += 1;
60 }
61 len = new_len;
62 }
63
64 let limit = const_min(len, new_len);
66 let mut j = 0;
67 while j < limit {
68 if let Some(existing) = dependency_by_radius[j]
69 && status.get_index() > existing.get_index()
70 {
71 dependency_by_radius[j] = Some(status);
72 }
73 j += 1;
74 }
75
76 i += 1;
77 }
78
79 let radius_by_dependency = Self::build_radius_lookup(&dependency_by_radius, len);
81
82 Self {
83 dependency_by_radius,
84 len,
85 radius_by_dependency,
86 }
87 }
88
89 const fn build_radius_lookup(
91 deps: &[Option<ChunkStatus>; MAX_RADIUS],
92 len: usize,
93 ) -> [usize; STATUS_COUNT] {
94 let mut radius_by_dependency = [0usize; STATUS_COUNT];
95 let mut radius = 0;
96 while radius < len {
97 if let Some(dep) = deps[radius] {
98 let index = dep.get_index();
99 let mut j = 0;
100 while j <= index && j < STATUS_COUNT {
101 radius_by_dependency[j] = radius;
102 j += 1;
103 }
104 }
105 radius += 1;
106 }
107 radius_by_dependency
108 }
109
110 const fn accumulate(&self, parent_accumulated: &Self, parent_status: ChunkStatus) -> Self {
112 let mut radius_of_parent = 0;
114 let mut i = 0;
115 while i < self.len {
116 if let Some(s) = self.dependency_by_radius[i]
117 && s.get_index() >= parent_status.get_index()
118 {
119 radius_of_parent = i;
120 }
121 i += 1;
122 }
123
124 let parent_len = parent_accumulated.len;
125 let new_len = const_max(radius_of_parent + parent_len, self.len);
126 let capped_len = const_min(new_len, MAX_RADIUS);
127
128 let mut accumulated = [None; MAX_RADIUS];
129
130 let mut dist = 0;
131 while dist < capped_len {
132 let dist_in_parent = dist.saturating_sub(radius_of_parent);
133
134 let parent_dep = if dist_in_parent < parent_accumulated.len {
135 parent_accumulated.dependency_by_radius[dist_in_parent]
136 } else {
137 None
138 };
139
140 let direct_dep = if dist < self.len {
141 self.dependency_by_radius[dist]
142 } else {
143 None
144 };
145
146 accumulated[dist] = const_max_status(direct_dep, parent_dep);
147 dist += 1;
148 }
149
150 let radius_by_dependency = Self::build_radius_lookup(&accumulated, capped_len);
151
152 Self {
153 dependency_by_radius: accumulated,
154 len: capped_len,
155 radius_by_dependency,
156 }
157 }
158
159 #[must_use]
164 pub const fn get_radius_of(&self, status: ChunkStatus) -> usize {
165 self.radius_by_dependency[status.get_index()]
166 }
167
168 #[must_use]
170 pub const fn get_radius(&self) -> usize {
171 self.len.saturating_sub(1)
172 }
173
174 #[must_use]
176 pub const fn get(&self, distance: usize) -> Option<ChunkStatus> {
177 if distance < self.len {
178 self.dependency_by_radius[distance]
179 } else {
180 None
181 }
182 }
183}
184
185const fn const_max(a: usize, b: usize) -> usize {
186 if a > b { a } else { b }
187}
188
189const fn const_min(a: usize, b: usize) -> usize {
190 if a < b { a } else { b }
191}
192
193const fn const_max_status(a: Option<ChunkStatus>, b: Option<ChunkStatus>) -> Option<ChunkStatus> {
194 match (a, b) {
195 (Some(sa), Some(sb)) => {
196 if sa.get_index() > sb.get_index() {
197 Some(sa)
198 } else {
199 Some(sb)
200 }
201 }
202 (Some(s), None) | (None, Some(s)) => Some(s),
203 (None, None) => None,
204 }
205}
206
207pub type ChunkStatusTask =
209 fn(Arc<WorldGenContext>, &ChunkStep, &Arc<StaticCache2D<Arc<ChunkHolder>>>, Arc<ChunkHolder>);
210
211#[derive(Clone, Copy)]
213pub struct ChunkStep {
214 pub target_status: ChunkStatus,
216 pub direct_dependencies: ChunkDependencies,
218 pub accumulated_dependencies: ChunkDependencies,
220 pub block_state_write_radius: i32,
222 pub task: ChunkStatusTask,
224}
225
226impl ChunkStep {
227 const PLACEHOLDER: Self = Self {
229 target_status: ChunkStatus::Empty,
230 direct_dependencies: ChunkDependencies::EMPTY,
231 accumulated_dependencies: ChunkDependencies::EMPTY,
232 block_state_write_radius: -1,
233 task: noop_task,
234 };
235
236 #[must_use]
238 pub const fn get_accumulated_radius_of(&self, status: ChunkStatus) -> usize {
239 if status.get_index() == self.target_status.get_index() {
240 0
241 } else {
242 self.accumulated_dependencies.get_radius_of(status)
243 }
244 }
245}
246
247fn noop_task(
248 _context: Arc<WorldGenContext>,
249 _step: &ChunkStep,
250 _cache: &Arc<StaticCache2D<Arc<ChunkHolder>>>,
251 _holder: Arc<ChunkHolder>,
252) {
253}
254
255pub struct ChunkPyramid {
257 steps: [ChunkStep; STATUS_COUNT],
258}
259
260impl ChunkPyramid {
261 #[must_use]
263 pub const fn get_step_to(&self, status: ChunkStatus) -> &ChunkStep {
264 &self.steps[status.get_index()]
265 }
266}
267
268struct ConstPyramidBuilder {
270 steps: [ChunkStep; STATUS_COUNT],
271 count: usize,
272}
273
274impl ConstPyramidBuilder {
275 const fn new() -> Self {
276 Self {
277 steps: [ChunkStep::PLACEHOLDER; STATUS_COUNT],
278 count: 0,
279 }
280 }
281
282 const fn step(
283 mut self,
284 status: ChunkStatus,
285 requirements: &[(ChunkStatus, usize)],
286 block_state_write_radius: i32,
287 task: ChunkStatusTask,
288 ) -> Self {
289 let (parent_status, parent_accumulated) = if self.count > 0 {
291 let parent = &self.steps[self.count - 1];
292 (
293 Some(parent.target_status),
294 Some(parent.accumulated_dependencies),
295 )
296 } else {
297 (None, None)
298 };
299
300 let direct = ChunkDependencies::from_requirements(requirements, parent_status);
302
303 let accumulated = match (parent_status, parent_accumulated) {
305 (Some(ps), Some(pa)) => direct.accumulate(&pa, ps),
306 _ => direct,
307 };
308
309 self.steps[self.count] = ChunkStep {
310 target_status: status,
311 direct_dependencies: direct,
312 accumulated_dependencies: accumulated,
313 block_state_write_radius,
314 task,
315 };
316 self.count += 1;
317 self
318 }
319
320 const fn build(self) -> ChunkPyramid {
321 ChunkPyramid { steps: self.steps }
322 }
323}
324
325macro_rules! define_pyramid {
340 (
341 $vis:vis const $name:ident = {
342 $($status:ident => {
343 $(requirements: [$( ($req_status:ident, $req_radius:expr) ),* $(,)?] ,)?
344 $(block_state_write_radius: $bswr:expr ,)?
345 task: $task:expr $(,)?
346 }),* $(,)?
347 };
348 ) => {
349 #[expect(missing_docs, reason = "generated pyramid constant; name is self-documenting")]
350 $vis const $name: &'static ChunkPyramid = &{
351 ConstPyramidBuilder::new()
352 $(
353 .step(
354 ChunkStatus::$status,
355 &[ $( $( (ChunkStatus::$req_status, $req_radius) ),* )? ],
356 define_pyramid!(@bswr $($bswr)?),
357 $task,
358 )
359 )*
360 .build()
361 };
362 };
363
364 (@bswr) => { -1 };
366 (@bswr $bswr:expr) => { $bswr };
367}
368
369define_pyramid! {
370 pub const GENERATION_PYRAMID = {
371 Empty => {
372 task: ChunkStatusTasks::empty,
373 },
374 StructureStarts => {
375 task: ChunkStatusTasks::generate_structure_starts,
376 },
377 StructureReferences => {
378 requirements: [(StructureStarts, 8)],
379 task: ChunkStatusTasks::generate_structure_references,
380 },
381 Biomes => {
382 requirements: [(StructureStarts, 8)],
383 task: ChunkStatusTasks::generate_biomes,
384 },
385 Noise => {
386 requirements: [(StructureStarts, 8), (Biomes, 1)],
387 block_state_write_radius: 0,
388 task: ChunkStatusTasks::generate_noise,
389 },
390 Surface => {
391 requirements: [(StructureStarts, 8), (Biomes, 1)],
392 block_state_write_radius: 0,
393 task: ChunkStatusTasks::generate_surface,
394 },
395 Carvers => {
396 requirements: [(StructureStarts, 8)],
397 block_state_write_radius: 0,
398 task: ChunkStatusTasks::generate_carvers,
399 },
400 Features => {
401 requirements: [(StructureStarts, 8), (Carvers, 1)],
402 block_state_write_radius: 1,
403 task: ChunkStatusTasks::generate_features,
404 },
405 InitializeLight => {
406 task: ChunkStatusTasks::initialize_light,
407 },
408 Light => {
409 requirements: [(InitializeLight, 1)],
410 block_state_write_radius: 0,
411 task: ChunkStatusTasks::light,
412 },
413 Spawn => {
414 requirements: [(Biomes, 1)],
415 task: ChunkStatusTasks::generate_spawn,
416 },
417 Full => {
418 task: ChunkStatusTasks::full,
419 },
420 };
421}
422
423define_pyramid! {
424 pub const LOADING_PYRAMID = {
425 Empty => {
426 task: noop_task,
427 },
428 StructureStarts => {
429 task: ChunkStatusTasks::load_structure_starts,
430 },
431 StructureReferences => {
432 task: noop_task,
433 },
434 Biomes => {
435 task: noop_task,
436 },
437 Noise => {
438 task: noop_task,
439 },
440 Surface => {
441 task: noop_task,
442 },
443 Carvers => {
444 task: noop_task,
445 },
446 Features => {
447 task: noop_task,
448 },
449 InitializeLight => {
450 task: ChunkStatusTasks::initialize_light,
451 },
452 Light => {
453 requirements: [(InitializeLight, 1)],
454 task: ChunkStatusTasks::load_light,
455 },
456 Spawn => {
457 task: noop_task,
458 },
459 Full => {
460 task: ChunkStatusTasks::full,
461 },
462 };
463}