gol_client/persistence/
load_board.rs

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// Visual
30
31#[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// Neighbor
44
45#[derive(Serialize, Deserialize)]
46#[serde(tag = "type")]
47enum NeighborRuleConfig {
48    Moore { margin: usize },
49    MooreWrap { margin: usize },
50}
51
52// State
53
54#[derive(Serialize, Deserialize)]
55#[serde(tag = "type")]
56enum StateConfig {
57    UInt { count: usize },
58}
59
60// State
61
62#[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// Board
79
80#[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// Cellular Automaton
101
102#[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), // Header with shape and number of states.
454                    > = 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}