1use std::{collections::HashSet, convert::Infallible};
2
3use num::rational::Ratio;
4use rand::{Rng, seq::SliceRandom};
5use rand_distr::{Triangular, TriangularError};
6use thedes_async_util::progress;
7use thedes_domain::{
8 geometry::{Coord, CoordPair},
9 map::Map,
10};
11use thedes_geometry::orientation::Direction;
12use thiserror::Error;
13use tokio::task;
14
15use crate::random::{MutableDistribution, PickedReproducibleRng};
16
17use super::Layer;
18
19#[derive(Debug, Error)]
20pub enum InitError {
21 #[error("Error creating random distribution for map layer's region count")]
22 CountDist(#[source] TriangularError),
23}
24
25#[derive(Debug, Error)]
26pub enum Error<Le, De, Ce> {
27 #[error("Error manipulating map layer")]
28 Layer(#[source] Le),
29 #[error("Error generating data of a map region")]
30 DataDistr(#[source] De),
31 #[error("Error collecting regions for outside generation components")]
32 Collection(#[source] Ce),
33}
34
35#[derive(Debug, Clone, Error)]
36pub enum InvalidRegionConfig {
37 #[error(
38 "Minimum region count ratio {min} cannot be greater than maximum {max}"
39 )]
40 CountBoundOrder { min: Ratio<Coord>, max: Ratio<Coord> },
41 #[error(
42 "Peak ratio of region count distribution {peak} must be between min \
43 and max rationes {min} and {max}"
44 )]
45 PeakOutOfBounds { min: Ratio<Coord>, peak: Ratio<Coord>, max: Ratio<Coord> },
46 #[error("Range must be in the interval [0, 1], given {ratio}")]
47 RatioRange { ratio: Ratio<Coord> },
48}
49
50pub trait Collector<T> {
51 type Error: std::error::Error;
52
53 fn add_region(
54 &mut self,
55 center: CoordPair,
56 data: &T,
57 ) -> Result<(), Self::Error>;
58
59 fn add_point(
60 &mut self,
61 region: usize,
62 point: CoordPair,
63 ) -> Result<(), Self::Error>;
64}
65
66impl<'a, C, T> Collector<T> for &'a mut C
67where
68 C: Collector<T> + ?Sized,
69{
70 type Error = C::Error;
71
72 fn add_region(
73 &mut self,
74 center: CoordPair,
75 data: &T,
76 ) -> Result<(), Self::Error> {
77 (**self).add_region(center, data)
78 }
79
80 fn add_point(
81 &mut self,
82 region: usize,
83 point: CoordPair,
84 ) -> Result<(), Self::Error> {
85 (**self).add_point(region, point)
86 }
87}
88
89#[derive(Debug, Clone, Copy)]
90pub struct NopCollector;
91
92impl<T> Collector<T> for NopCollector {
93 type Error = Infallible;
94
95 fn add_region(
96 &mut self,
97 _center: CoordPair,
98 _data: &T,
99 ) -> Result<(), Self::Error> {
100 Ok(())
101 }
102
103 fn add_point(
104 &mut self,
105 _region: usize,
106 _point: CoordPair,
107 ) -> Result<(), Self::Error> {
108 Ok(())
109 }
110}
111
112#[derive(Debug, Clone)]
113pub struct Config {
114 min_region_count: Ratio<Coord>,
115 max_region_count: Ratio<Coord>,
116 peak_region_count: Ratio<Coord>,
117}
118
119impl Default for Config {
120 fn default() -> Self {
121 Self::new()
122 }
123}
124
125impl Config {
126 pub fn new() -> Self {
127 Self {
128 min_region_count: Ratio::new(1, 30),
129 max_region_count: Ratio::new(1, 10),
130 peak_region_count: Ratio::new(1, 20),
131 }
132 }
133
134 pub fn with_min_region_count(
135 self,
136 ratio: Ratio<Coord>,
137 ) -> Result<Self, InvalidRegionConfig> {
138 if ratio < Ratio::ZERO || ratio > Ratio::ONE {
139 Err(InvalidRegionConfig::RatioRange { ratio })?;
140 }
141 if ratio > self.max_region_count {
142 Err(InvalidRegionConfig::CountBoundOrder {
143 min: ratio,
144 max: self.max_region_count,
145 })?;
146 }
147 if ratio > self.peak_region_count {
148 Err(InvalidRegionConfig::PeakOutOfBounds {
149 min: ratio,
150 peak: self.peak_region_count,
151 max: self.max_region_count,
152 })?;
153 }
154 Ok(Self { min_region_count: ratio, ..self })
155 }
156
157 pub fn with_max_region_count(
158 self,
159 ratio: Ratio<Coord>,
160 ) -> Result<Self, InvalidRegionConfig> {
161 if ratio < Ratio::ZERO || ratio > Ratio::ONE {
162 Err(InvalidRegionConfig::RatioRange { ratio })?;
163 }
164 if self.min_region_count > ratio {
165 Err(InvalidRegionConfig::CountBoundOrder {
166 min: self.min_region_count,
167 max: ratio,
168 })?;
169 }
170 if self.peak_region_count > ratio {
171 Err(InvalidRegionConfig::PeakOutOfBounds {
172 min: self.min_region_count,
173 peak: self.peak_region_count,
174 max: ratio,
175 })?;
176 }
177 Ok(Self { max_region_count: ratio, ..self })
178 }
179
180 pub fn with_peak_region_count(
181 self,
182 ratio: Ratio<Coord>,
183 ) -> Result<Self, InvalidRegionConfig> {
184 if ratio < Ratio::ZERO || ratio > Ratio::ONE {
185 Err(InvalidRegionConfig::RatioRange { ratio })?;
186 }
187 if self.min_region_count > ratio || ratio > self.max_region_count {
188 Err(InvalidRegionConfig::PeakOutOfBounds {
189 min: self.min_region_count,
190 peak: ratio,
191 max: self.max_region_count,
192 })?;
193 }
194 Ok(Self { peak_region_count: ratio, ..self })
195 }
196
197 pub fn finish(
198 self,
199 map: &Map,
200 rng: &mut PickedReproducibleRng,
201 ) -> Result<Generator, InitError> {
202 let unified_size = map.rect().size.x + map.rect().size.y;
203 let mut actual_min =
204 (self.min_region_count * unified_size).ceil().to_integer();
205 let mut actual_peak =
206 (self.peak_region_count * unified_size).floor().to_integer();
207 let mut actual_max =
208 (self.max_region_count * unified_size).floor().to_integer();
209 actual_min = actual_min.max(unified_size);
210 actual_max = actual_max.min(unified_size);
211 actual_min = actual_min.min(actual_max);
212 actual_max = actual_min.max(actual_min);
213 actual_peak = actual_peak.max(actual_min).min(actual_max);
214 let min = f64::from(actual_min);
215 let max = f64::from(actual_max) + 1.0 - f64::EPSILON;
216 let mode = f64::from(actual_peak);
217 let distr =
218 Triangular::new(min, max, mode).map_err(InitError::CountDist)?;
219
220 let region_count = rng.sample(&distr) as usize;
221
222 Ok(Generator { region_count })
223 }
224}
225
226#[derive(Debug)]
227pub struct Generator {
228 region_count: usize,
229}
230
231impl Generator {
232 pub fn region_count(&self) -> usize {
233 self.region_count
234 }
235
236 pub fn progress_goal(&self, map: &Map) -> usize {
237 let area = map.rect().map(usize::from).total_area();
238 let region_count = self.region_count();
239 let region_data_prog = region_count;
240 let init_avail_points_prog = area;
241 let shuf_avail_points_prog = 1;
242 let init_centers_prog = 1;
243 let convert_avail_points_prog = 1;
244 let init_frontiers_prog = region_count;
245 let shuf_expand_prog = area - region_count;
246 region_data_prog
247 + init_avail_points_prog
248 + shuf_avail_points_prog
249 + init_centers_prog
250 + convert_avail_points_prog
251 + init_frontiers_prog
252 + shuf_expand_prog
253 }
254
255 pub async fn execute<L, Dd, C>(
256 self,
257 layer: &L,
258 data_distr: &mut Dd,
259 map: &mut Map,
260 rng: &mut PickedReproducibleRng,
261 collector: &mut C,
262 progress_logger: progress::Logger,
263 ) -> Result<(), Error<L::Error, Dd::Error, C::Error>>
264 where
265 L: Layer,
266 L::Data: Clone,
267 Dd: MutableDistribution<L::Data>,
268 C: Collector<L::Data>,
269 {
270 let area = map.rect().map(usize::from).total_area();
271
272 let mut execution = Execution {
273 region_count: self.region_count,
274 regions_data: Vec::with_capacity(self.region_count),
275 region_centers: Vec::with_capacity(self.region_count),
276 available_points_seq: Vec::with_capacity(area),
277 available_points: HashSet::with_capacity(area),
278 region_frontiers: Vec::with_capacity(self.region_count),
279 to_be_processed: Vec::new(),
280 layer,
281 data_distr,
282 map,
283 rng,
284 collector,
285 progress_logger,
286 };
287
288 execution.generate_region_data().await?;
289 execution.initialize_available_points().await?;
290 execution.shuffle_available_points().await?;
291 execution.initialize_centers().await?;
292 execution.converting_available_points().await?;
293 execution.initialize_region_frontiers().await?;
294 execution.expanding_region_frontiers().await?;
295
296 execution.progress_logger.set_status("done");
297
298 Ok(())
299 }
300}
301
302#[derive(Debug)]
303struct Execution<'a, D, L, Dd, C> {
304 region_count: usize,
305 regions_data: Vec<D>,
306 region_centers: Vec<CoordPair>,
307 available_points_seq: Vec<CoordPair>,
308 available_points: HashSet<CoordPair>,
309 region_frontiers: Vec<(usize, CoordPair)>,
310 to_be_processed: Vec<(usize, CoordPair)>,
311 layer: &'a L,
312 data_distr: &'a mut Dd,
313 map: &'a mut Map,
314 rng: &'a mut PickedReproducibleRng,
315 collector: &'a mut C,
316 progress_logger: progress::Logger,
317}
318
319impl<'a, L, Dd, C> Execution<'a, L::Data, L, Dd, C>
320where
321 L: Layer,
322 L::Data: Clone,
323 Dd: MutableDistribution<L::Data>,
324 C: Collector<L::Data>,
325{
326 pub async fn generate_region_data(
327 &mut self,
328 ) -> Result<(), Error<L::Error, Dd::Error, C::Error>> {
329 self.progress_logger.set_status("generating region data");
330 while self.regions_data.len() < self.region_count {
331 let region_data = self
332 .data_distr
333 .sample_mut(self.rng)
334 .map_err(Error::DataDistr)?;
335 self.regions_data.push(region_data);
336 self.progress_logger.increment();
337 task::yield_now().await;
338 }
339 Ok(())
340 }
341
342 pub async fn initialize_available_points(
343 &mut self,
344 ) -> Result<(), Error<L::Error, Dd::Error, C::Error>> {
345 self.progress_logger.set_status("initializing available points");
346 let map_rect = self.map.rect();
347 for y in map_rect.top_left.y .. map_rect.bottom_right().y {
348 for x in map_rect.top_left.x .. map_rect.bottom_right().x {
349 let point = CoordPair { y, x };
350 self.available_points_seq.push(point);
351 self.progress_logger.increment();
352 task::yield_now().await;
353 }
354 }
355 Ok(())
356 }
357
358 pub async fn shuffle_available_points(
359 &mut self,
360 ) -> Result<(), Error<L::Error, Dd::Error, C::Error>> {
361 self.progress_logger.set_status("shuffling available points");
362 self.available_points_seq.shuffle(self.rng);
363 self.progress_logger.increment();
364 task::yield_now().await;
365 Ok(())
366 }
367
368 pub async fn initialize_centers(
369 &mut self,
370 ) -> Result<(), Error<L::Error, Dd::Error, C::Error>> {
371 self.progress_logger.set_status("initializing region centers");
372 let drained = self
373 .available_points_seq
374 .drain(self.available_points_seq.len() - self.region_count ..);
375 for (center, data) in drained.zip(&mut self.regions_data) {
376 self.collector
377 .add_region(center, data)
378 .map_err(Error::Collection)?;
379 self.region_centers.push(center);
380 }
381 self.progress_logger.increment();
382 task::yield_now().await;
383 Ok(())
384 }
385
386 pub async fn converting_available_points(
387 &mut self,
388 ) -> Result<(), Error<L::Error, Dd::Error, C::Error>> {
389 self.progress_logger.set_status("converting available points");
390 self.available_points.extend(self.available_points_seq.drain(..));
391 self.progress_logger.increment();
392 task::yield_now().await;
393 Ok(())
394 }
395
396 pub async fn initialize_region_frontiers(
397 &mut self,
398 ) -> Result<(), Error<L::Error, Dd::Error, C::Error>> {
399 self.progress_logger.set_status("initializing region frontiers");
400 for region in 0 .. self.region_count {
401 self.expand_point(region, self.region_centers[region])?;
402 self.progress_logger.increment();
403 task::yield_now().await;
404 }
405 Ok(())
406 }
407
408 pub async fn expanding_region_frontiers(
409 &mut self,
410 ) -> Result<(), Error<L::Error, Dd::Error, C::Error>> {
411 self.progress_logger.set_status("expanding region frontiers");
412 while !self.available_points.is_empty() {
413 self.region_frontiers.shuffle(self.rng);
414 let process_count = (self.region_frontiers.len() - 1).max(1);
415 let start = self.region_frontiers.len() - process_count;
416 let drained = self.region_frontiers.drain(start ..);
417 self.to_be_processed.extend(drained);
418
419 while let Some((region, point)) = self.to_be_processed.pop() {
420 if self.available_points.remove(&point) {
421 self.expand_point(region, point)?;
422 self.progress_logger.increment();
423 task::yield_now().await;
424 }
425 }
426 }
427 Ok(())
428 }
429
430 fn expand_point(
431 &mut self,
432 region: usize,
433 point: CoordPair,
434 ) -> Result<(), Error<L::Error, Dd::Error, C::Error>> {
435 self.layer
436 .set(self.map, point, self.regions_data[region].clone())
437 .map_err(Error::Layer)?;
438 for direction in Direction::ALL {
439 if let Some(new_point) = point
440 .checked_move_unit(direction)
441 .filter(|new_point| self.available_points.contains(new_point))
442 {
443 self.collector
444 .add_point(region, new_point)
445 .map_err(Error::Collection)?;
446 self.region_frontiers.push((region, new_point));
447 }
448 }
449 Ok(())
450 }
451}