1use crate::persistence::{
2 batch_serializer::BatchIndexedSerializer,
3 batch_serializer_local::{BatchSerializerLocal, StateSerializerLocal},
4};
5use gol_core::{
6 util::grid_util::Shape2D, Board, BoardCallback, BoardNeighborManager, BoardSpaceManager,
7 BoardStateManager, BoardStrategyManager, DecayLifeLikeStrategy, Grid, GridFactory, GridPoint2D,
8 IndexedDataOwned, NeighborMoore, NeighborMooreDonut, NeighborMooreTriangle, NeighborsGridDonut,
9 NeighborsGridSurround, SharedStrategyManager, SparseStates, StandardBoard, StatesCallback,
10};
11use gol_renderer::{
12 CellularAutomatonRenderer, DiscreteStateCharMap, DiscreteStateColorMap,
13 GraphicalRendererGrid2D, StateVisualMapping,
14};
15use num_cpus;
16use rand::Rng;
17use rayon::prelude::*;
18use rgb::RGBA16;
19use serde::{Deserialize, Serialize};
20use serde_json;
21use std::collections::{HashMap, HashSet};
22use std::iter::FromIterator;
23use std::thread;
24use std::time::Duration;
25
26type IntIdx = i32;
27type IntState = u8;
28
29#[derive(Serialize, Deserialize)]
32pub enum VisualStyle {
33 Ascii,
34 Graphical,
35}
36
37#[derive(Serialize, Deserialize)]
38pub struct VisualConfig {
39 on: bool,
40 styles: Vec<VisualStyle>,
41}
42
43#[derive(Serialize, Deserialize)]
46#[serde(tag = "type")]
47enum NeighborRuleConfig {
48 Moore { margin: usize },
49 MooreWrap { margin: usize },
50}
51
52#[derive(Serialize, Deserialize)]
55#[serde(tag = "type")]
56enum StateConfig {
57 UInt { count: usize },
58}
59
60#[derive(Serialize, Deserialize)]
63#[serde(untagged)]
64enum CellCount {
65 Integer(usize),
66 Range(Vec<usize>),
67}
68
69#[derive(Serialize, Deserialize)]
70#[serde(tag = "type")]
71enum EvolutionRuleConfig {
72 AliveCount {
73 survive: Vec<CellCount>,
74 born: Vec<CellCount>,
75 },
76}
77
78#[derive(Serialize, Deserialize)]
81#[serde(tag = "type")]
82enum InitialStatesConfig {
83 Deterministic {
84 positions: HashMap<String, Vec<GridPoint2D<IntIdx>>>,
85 },
86 Random {
87 alive_ratio: f32,
88 },
89}
90
91#[derive(Serialize, Deserialize)]
92#[serde(tag = "type")]
93enum BoardConfig {
94 Grid2D {
95 shape: Shape2D,
96 initial_states: InitialStatesConfig,
97 },
98}
99
100#[derive(Serialize, Deserialize)]
103pub struct CellularAutomatonConfig {
104 title: String,
105 max_iter: Option<usize>,
106 delay: f64,
107 pause_at_start: bool,
108 enable_control: bool,
109 visual: VisualConfig,
110 neighbor_rule: NeighborRuleConfig,
111 state: StateConfig,
112 evolution_rule: EvolutionRuleConfig,
113 board: BoardConfig,
114}
115
116impl CellularAutomatonConfig {
117 pub fn from_json(json: &str) -> Self {
118 serde_json::from_str(json).unwrap()
119 }
120
121 pub fn title(&self) -> &String {
122 &self.title
123 }
124
125 pub fn run_board(&self, save_dir: Option<String>, is_triangular: bool) {
126 let max_iter = self.max_iter.clone();
127 let (mut char_renderers, mut color_renderers) = match self.board {
128 BoardConfig::Grid2D {
129 shape: _,
130 initial_states: _,
131 } => {
132 let space = self.gen_space_grid_2d().unwrap();
133 let neighbor = self.gen_neighbor_grid_2d(is_triangular).unwrap();
134 match self.state {
135 StateConfig::UInt { count: _ } => {
136 let state = self.gen_state_manager_grid_2d_discrete().unwrap();
137 let strat = self.gen_strat_grid_2d_discrete().unwrap();
138 let (callbacks, char_renderers, color_renderers) =
139 self.gen_callback_grid_2d_discrete_state(save_dir, is_triangular);
140 let mut board =
141 StandardBoard::new(space, neighbor, state, strat, callbacks);
142 std::thread::spawn(move || {
143 board.advance(max_iter);
144 });
145 (char_renderers, color_renderers)
146 }
147 }
148 }
149 };
150
151 if char_renderers.len() + color_renderers.len() == 1 {
152 match char_renderers.first() {
153 Some(_) => char_renderers[0].as_mut().run(self.char_maps_discrete()),
154 None => color_renderers[0].as_mut().run(self.color_maps_discrete()),
155 }
156 } else {
157 let mut main_renderer = None;
158 let mut handles = Vec::with_capacity(char_renderers.len() + color_renderers.len());
159 while !char_renderers.is_empty() {
160 let mut cur = char_renderers.pop().unwrap();
161 let char_map = self.char_maps_discrete();
162 handles.push(thread::spawn(move || cur.run(char_map)));
163 }
164 while !color_renderers.is_empty() {
165 let mut cur = color_renderers.pop().unwrap();
166 if cur.need_run_on_main() {
167 main_renderer = Some(cur);
168 } else {
169 let color_map = self.color_maps_discrete();
170 handles.push(thread::spawn(move || cur.run(color_map)));
171 }
172 }
173 match main_renderer {
174 Some(mut renderer) => renderer.run(self.color_maps_discrete()),
175 None => {
176 for handle in handles {
177 handle.join().unwrap()
178 }
179 }
180 }
181 }
182 }
183
184 fn gen_space_grid_2d(
185 &self,
186 ) -> Result<
187 Box<
188 dyn BoardSpaceManager<
189 GridPoint2D<IntIdx>,
190 std::vec::IntoIter<GridPoint2D<IntIdx>>,
191 rayon::vec::IntoIter<GridPoint2D<IntIdx>>,
192 >,
193 >,
194 (),
195 > {
196 match &self.board {
197 BoardConfig::Grid2D {
198 shape,
199 initial_states: _,
200 } => {
201 let shape_vec = vec![shape.width(), shape.height()];
202 let space_manager = Grid::<GridPoint2D<IntIdx>>::new(shape_vec.into_iter());
203 Ok(Box::new(space_manager))
204 }
205 }
206 }
207
208 fn gen_neighbor_grid_2d(
209 &self,
210 is_triangular: bool,
211 ) -> Result<
212 Box<dyn BoardNeighborManager<GridPoint2D<IntIdx>, std::vec::IntoIter<GridPoint2D<IntIdx>>>>,
213 (),
214 > {
215 match &self.neighbor_rule {
216 NeighborRuleConfig::Moore { margin } => {
217 if margin == &1 {
218 Ok(if is_triangular {
219 Box::new(NeighborMooreTriangle::new())
220 } else {
221 Box::new(NeighborMoore::new())
222 })
223 } else {
224 Ok(Box::new(NeighborsGridSurround::new(margin.clone())))
225 }
226 }
227 NeighborRuleConfig::MooreWrap { margin } => {
228 let shape = match &self.board {
229 BoardConfig::Grid2D {
230 shape,
231 initial_states: _,
232 } => shape,
233 };
234 if margin == &1 {
235 Ok(Box::new(NeighborMooreDonut::new(shape.clone())))
236 } else {
237 Ok(Box::new(NeighborsGridDonut::new(
238 margin.clone(),
239 [shape.width(), shape.height()].iter().cloned(),
240 )))
241 }
242 }
243 }
244 }
245
246 fn gen_state_manager_grid_2d_discrete(
247 &self,
248 ) -> Result<
249 Box<
250 dyn BoardStateManager<
251 IntState,
252 GridPoint2D<IntIdx>,
253 rayon::vec::IntoIter<IndexedDataOwned<GridPoint2D<IntIdx>, IntState>>,
254 >,
255 >,
256 (),
257 > {
258 match &self.state {
259 StateConfig::UInt { count } => {
260 let init_states = match &self.board {
261 BoardConfig::Grid2D {
262 shape,
263 initial_states,
264 } => match initial_states {
265 InitialStatesConfig::Deterministic { positions } => positions
266 .par_iter()
267 .map(|(key, val)| {
268 let cur_map: HashMap<GridPoint2D<IntIdx>, IntState> = val
269 .par_iter()
270 .map(|ele| {
271 (
272 ele.clone(),
273 key.parse::<IntState>().expect(
274 "Discrete states must be unsigned integers.",
275 ),
276 )
277 })
278 .collect();
279 cur_map
280 })
281 .reduce(|| HashMap::new(), |a, b| a.into_iter().chain(b).collect()),
282 InitialStatesConfig::Random { alive_ratio } => {
283 gen_2d_random_discrete_states(shape, alive_ratio, count)
284 }
285 },
286 };
287 Ok(Box::new(SparseStates::new(0, init_states)))
288 }
289 }
290 }
291
292 fn gen_strat_grid_2d_discrete(
293 &self,
294 ) -> Result<
295 Box<
296 dyn BoardStrategyManager<
297 GridPoint2D<IntIdx>,
298 IntState,
299 std::vec::IntoIter<IndexedDataOwned<GridPoint2D<IntIdx>, IntState>>,
300 >,
301 >,
302 (),
303 > {
304 let state_count = match &self.state {
305 StateConfig::UInt { count } => count,
306 };
307 match &self.evolution_rule {
308 EvolutionRuleConfig::AliveCount { survive, born } => Ok(Box::new(
309 SharedStrategyManager::new(Box::new(DecayLifeLikeStrategy::new(
310 state_count.clone(),
311 collect_cell_counts(&survive),
312 collect_cell_counts(&born),
313 ))),
314 )),
315 }
316 }
317
318 fn gen_callback_grid_2d_discrete_state(
319 &self,
320 save_dir: Option<String>,
321 is_triangular: bool,
322 ) -> (
323 Vec<
324 BoardCallback<
325 IntState,
326 GridPoint2D<IntIdx>,
327 rayon::vec::IntoIter<IndexedDataOwned<GridPoint2D<IntIdx>, IntState>>,
328 >,
329 >,
330 Vec<Box<dyn CellularAutomatonRenderer<IntState, char>>>,
331 Vec<Box<dyn CellularAutomatonRenderer<IntState, RGBA16>>>,
332 ) {
333 let mut callbacks = Vec::new();
334 let mut char_renderers: Vec<Box<dyn CellularAutomatonRenderer<IntState, char>>> =
335 Vec::new();
336 let mut color_renderers: Vec<Box<dyn CellularAutomatonRenderer<IntState, RGBA16>>> =
337 Vec::new();
338
339 if self.visual.on && !self.visual.styles.is_empty() {
340 let one_billion_nano_sec: f64 = 1_000_000_000f64;
341 let interval_nano_sec = (self.delay * one_billion_nano_sec) as u64;
342 let keyboard_control = if self.enable_control {
343 let (control_callbacks, keyboard_control) =
344 crate::callback::standard_control_callbacks(
345 self.pause_at_start,
346 Duration::from_nanos(interval_nano_sec),
347 );
348 callbacks = control_callbacks;
349 Some(keyboard_control)
350 } else {
351 None
352 };
353 let board_shape = match &self.board {
354 BoardConfig::Grid2D {
355 shape,
356 initial_states: _,
357 } => shape.clone(),
358 };
359 let states_callback: StatesCallback<GridPoint2D<IntIdx>, IntState> =
360 StatesCallback::new(0);
361 let states_read_only = states_callback.clone_read_only();
362 let states_callback = BoardCallback::WithStates(Box::new(states_callback));
363 callbacks.push(states_callback);
364
365 for style in self.visual.styles.iter() {
366 match style {
367 VisualStyle::Graphical => {
368 let graphical_renderer = GraphicalRendererGrid2D::new(
369 board_shape.width(),
370 board_shape.height(),
371 states_read_only.clone(),
372 );
373
374 match graphical_renderer {
375 Ok(val) => {
376 let mut real_gui_renderer = val.with_title(self.title.clone());
377 if is_triangular {
378 real_gui_renderer = real_gui_renderer.with_triangles();
379 }
380
381 let res = match &keyboard_control {
382 Some(control) => {
383 real_gui_renderer.with_keyboard_control(control.clone())
384 }
385 None => real_gui_renderer,
386 };
387 color_renderers.push(Box::new(res));
388 }
389 Err(err) => eprintln!("Error creating graphical renderer: {:?}", err),
390 };
391 }
392 VisualStyle::Ascii => {
393 #[cfg(not(feature = "ascii"))]
394 eprintln!("Cannot create ASCII renderer, please recompile with \"--features ascii\",");
395 #[cfg(feature = "ascii")]
396 {
397 use gol_renderer::TextRendererGrid2D;
398
399 let text_renderer = TextRendererGrid2D::new(
400 board_shape.width(),
401 board_shape.height(),
402 states_read_only.clone(),
403 )
404 .with_title(self.title.clone());
405 let res = match &keyboard_control {
406 Some(control) => {
407 text_renderer.with_keyboard_control(control.clone())
408 }
409 None => text_renderer,
410 };
411 char_renderers.push(Box::new(res));
412 }
413 }
414 }
415 }
416 }
417
418 let mut found_must_main_thread = false;
419
420 for renderer in char_renderers.iter() {
421 if renderer.need_run_on_main() {
422 if found_must_main_thread {
423 panic!("More than one visual style need to be ran on main thread, try reducing the number of styles.");
424 } else {
425 found_must_main_thread = true;
426 }
427 }
428 }
429
430 for renderer in color_renderers.iter() {
431 if renderer.need_run_on_main() {
432 if found_must_main_thread {
433 panic!("More than one visual style need to be ran on main thread, try reducing the number of styles.");
434 } else {
435 found_must_main_thread = true;
436 }
437 }
438 }
439
440 if save_dir.is_some() {
441 let dir = save_dir.unwrap();
442 match &self.board {
443 BoardConfig::Grid2D {
444 shape,
445 initial_states: _,
446 } => {
447 let num_states = match &self.state {
448 StateConfig::UInt { count } => count,
449 }
450 .clone();
451 let serializer: BatchIndexedSerializer<
452 Vec<IndexedDataOwned<GridPoint2D<IntIdx>, IntState>>,
453 (Shape2D, usize), > = BatchIndexedSerializer::new(100).with_header((shape.clone(), num_states));
455 let serializer = BatchSerializerLocal::new(&dir, serializer);
456 let serializer = StateSerializerLocal::new(serializer, 0);
457 callbacks.push(BoardCallback::WithStates(Box::new(serializer)));
458 }
459 }
460 }
461
462 (callbacks, char_renderers, color_renderers)
463 }
464
465 fn char_maps_discrete(&self) -> Box<dyn StateVisualMapping<IntState, char>> {
466 match &self.state {
467 StateConfig::UInt { count } => Box::new(DiscreteStateCharMap::new(*count)),
468 }
469 }
470
471 fn color_maps_discrete(&self) -> Box<dyn StateVisualMapping<IntState, RGBA16>> {
472 match &self.state {
473 StateConfig::UInt { count } => Box::new(DiscreteStateColorMap::new(*count)),
474 }
475 }
476}
477
478fn collect_cell_counts(counts: &Vec<CellCount>) -> HashSet<usize> {
479 counts
480 .par_iter()
481 .map(|ele| match ele {
482 CellCount::Integer(val) => HashSet::from_iter([val.clone()].iter().cloned()),
483 CellCount::Range(range) => {
484 HashSet::from_iter(range.first().unwrap().clone()..=range.last().unwrap().clone())
485 }
486 })
487 .reduce(|| HashSet::new(), |a, b| a.union(&b).cloned().collect())
488}
489
490fn gen_random_usize(len: &usize, alive_ratio: &f32) -> HashSet<usize> {
491 let core_count = num_cpus::get();
492 let num_indices_per_thread = len / core_count + 1;
493 let mut state_indices: Vec<HashSet<usize>> = vec![HashSet::new(); core_count];
494 state_indices
495 .par_iter_mut()
496 .enumerate()
497 .for_each(|(i, ele)| {
498 let mut rng = rand::thread_rng();
499 for inner_idx in
500 (i * num_indices_per_thread)..std::cmp::min((i + 1) * num_indices_per_thread, *len)
501 {
502 if &rng.gen::<f32>() <= alive_ratio {
503 ele.insert(inner_idx);
504 }
505 }
506 });
507
508 state_indices
509 .into_par_iter()
510 .reduce(|| HashSet::new(), |a, b| a.union(&b).cloned().collect())
511}
512
513fn gen_2d_random_discrete_states(
514 board_shape: &Shape2D,
515 alive_ratio: &f32,
516 state_count: &usize,
517) -> HashMap<GridPoint2D<IntIdx>, IntState> {
518 let res = gen_random_usize(&board_shape.volume(), alive_ratio);
519 res.into_par_iter()
520 .map(|ele| {
521 let x = (ele % board_shape.width()) as i64 + board_shape.x_idx_min();
522 let y = (ele / board_shape.width()) as i64 + board_shape.y_idx_min();
523 (
524 GridPoint2D::new(x as IntIdx, y as IntIdx),
525 (state_count - 1) as IntState,
526 )
527 })
528 .collect()
529}