• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

lloydmeta / gol-rs / 6903528928

17 Nov 2023 11:43AM UTC coverage: 60.341% (-0.09%) from 60.435%
6903528928

push

github

lloydmeta
Use safe methods, which somehow speed up grid advancing by ~10%

Signed-off-by: lloydmeta <lloydmeta@gmail.com>

17 of 20 new or added lines in 2 files covered. (85.0%)

3 existing lines in 1 file now uncovered.

283 of 469 relevant lines covered (60.34%)

105049.74 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.04
/src/data/grid.rs
1
use super::cell::{Cell, Status};
2
use rand;
3
use rand::Rng;
4
use rayon::prelude::*;
5
use std::mem;
6

7
const PAR_THRESHOLD_AREA: usize = 250_000;
8

9
/// Used for indexing into the grid
10
#[allow(clippy::module_name_repetitions)]
11
#[derive(Debug, PartialEq, Eq)]
60,000✔
12
pub struct GridIdx(pub usize);
13

14
#[derive(Debug)]
1✔
15
pub struct Grid {
16
    /* Addressed by from-zero (i, j) notation, where i is row number, j is column number
17
     * such that given the following shows coordinates for cells in a 3 x 3 grid:
18
     *
19
     * [ (0,0) (0,1) (0,2) ]
20
     * [ (1,0) (1,1) (1,2) ]
21
     * [ (2,0) (2,1) (2,2) ]
22
     *
23
     * will get flattened into a single vector:
24
     * [ (0,0), (0,1), (0,2), (1,0), (1,1), (1,2), (2,0), (2,1), (2,2) ]
25
     */
26
    cells: Vec<Cell>,
27
    scratchpad_cells: Vec<Cell>,
28
    max_i: usize,
29
    max_j: usize,
30
    area: usize,
31
    area_requires_bool: bool,
32
    // Cache of where the neighbours are for each point
33
    neighbours: Vec<[GridIdx; 8]>,
34
}
35

36
#[derive(PartialEq, Eq, Debug, PartialOrd, Ord, Clone)]
×
37
pub struct Coord {
38
    pub i: usize,
39
    pub j: usize,
40
}
41

42
impl Grid {
43
    /// Creates a grid with the given width and height
44
    pub fn new(width: usize, height: usize) -> Self {
6✔
45
        let mut rng = rand::thread_rng();
6✔
46
        // Grid is a matrix with {height} rows and {width} columns, addressed
6✔
47
        // via (i, j) (row, column) convention. Used for finding neightbours because it's
6✔
48
        // just an easier mental model to work with for that problem. It gets flattened later.
6✔
49
        let mut grid = Vec::with_capacity(height);
6✔
50
        for _ in 0..height {
6✔
51
            let mut row = Vec::with_capacity(width);
167✔
52
            for _ in 0..width {
7,589✔
53
                let status = if rng.gen() {
7,589✔
54
                    Status::Alive
3,748✔
55
                } else {
56
                    Status::Dead
3,841✔
57
                };
58
                let cell = Cell(status);
7,589✔
59
                row.push(cell);
7,589✔
60
            }
61
            grid.push(row);
167✔
62
        }
63

64
        let max_i = if height == 0 { 0 } else { height - 1 };
6✔
65
        let max_j = if width == 0 { 0 } else { width - 1 };
6✔
66
        let neighbours = neighbours(max_i, max_j, &grid);
6✔
67
        let cells: Vec<Cell> = grid.into_iter().flatten().collect();
6✔
68
        let scratchpad_cells = cells.clone();
6✔
69
        let area = width * height;
6✔
70
        let area_requires_bool = area >= PAR_THRESHOLD_AREA;
6✔
71
        Self {
6✔
72
            cells,
6✔
73
            scratchpad_cells,
6✔
74
            max_i,
6✔
75
            max_j,
6✔
76
            area,
6✔
77
            area_requires_bool,
6✔
78
            neighbours,
6✔
79
        }
6✔
80
    }
6✔
81

82
    /// Returns the i-th Cell in a grid as if the 2 dimensional matrix
83
    /// has been flattened into a 1 dimensional one row-wise
84
    ///
85
    /// TODO: is using iter faster or slower than just doing the checks?
86
    pub fn get_idx(&self, &GridIdx(idx): &GridIdx) -> Option<&Cell> {
9✔
87
        self.cells.get(idx)
9✔
88
    }
9✔
89

90
    // TODO delete if not used
91
    pub const fn to_grid_idx(&self, &Coord { i, j }: &Coord) -> Option<GridIdx> {
28✔
92
        if i <= self.max_i && j <= self.max_j {
28✔
93
            Some(GridIdx(self.width() * i + j))
27✔
94
        } else {
95
            None
1✔
96
        }
97
    }
28✔
98

99
    // Returns a slice with references to this grid's cells
100
    pub fn cells(&self) -> Vec<Vec<&Cell>> {
3✔
101
        let mut rows = Vec::with_capacity(self.height());
3✔
102
        let mut i = 0;
3✔
103
        for _ in 0..self.height() {
3✔
104
            let mut columns = Vec::with_capacity(self.width());
13✔
105
            for _ in 0..self.width() {
109✔
106
                columns.push(&self.cells[i]);
109✔
107
                i += 1;
109✔
108
            }
109✔
109
            rows.push(columns);
13✔
110
        }
111
        rows
3✔
112
    }
3✔
113

114
    pub const fn height(&self) -> usize {
6✔
115
        self.max_i + 1
6✔
116
    }
6✔
117

118
    pub const fn width(&self) -> usize {
53✔
119
        self.max_j + 1
53✔
120
    }
53✔
121

UNCOV
122
    pub const fn area(&self) -> usize {
×
UNCOV
123
        self.area
×
UNCOV
124
    }
×
125

126
    pub const fn area_requires_bool(&self) -> bool {
100✔
127
        self.area_requires_bool
100✔
128
    }
100✔
129

130
    pub fn advance(&mut self) {
100✔
131
        {
100✔
132
            let neighbours = &self.neighbours;
100✔
133
            let last_gen = &self.cells;
100✔
134
            let area_requires_par = self.area_requires_bool();
100✔
135
            let cells = &mut self.scratchpad_cells;
100✔
136
            let cell_op = |(i, cell): (usize, &mut Cell)| {
750,000✔
137
                if let Some(neighbours_vec) = neighbours.get(i) {
750,000✔
138
                    let alives = neighbours_vec.iter().fold(0, |acc, &GridIdx(idx)| {
6,000,000✔
139
                        if let Some(last_gen_status) = last_gen.get(idx) {
6,000,000✔
140
                            if last_gen_status.0 == Status::Alive {
6,000,000✔
141
                                acc + 1
981,520✔
142
                            } else {
143
                                acc
5,018,480✔
144
                            }
145
                        } else {
NEW
146
                            acc
×
147
                        }
148
                    });
6,000,000✔
149
                    if let Some(last_gen_cell) = last_gen.get(i) {
750,000✔
150
                        let next_status = last_gen_cell.next_status(alives);
750,000✔
151
                        cell.update(next_status);
750,000✔
152
                    }
750,000✔
NEW
153
                }
×
154
            };
750,000✔
155
            if area_requires_par {
100✔
156
                cells.par_iter_mut().enumerate().for_each(cell_op);
×
157
            } else {
×
158
                for (i, cell) in cells.iter_mut().enumerate() {
750,000✔
159
                    cell_op((i, cell));
750,000✔
160
                }
750,000✔
161
            }
162
        }
163
        mem::swap(&mut self.cells, &mut self.scratchpad_cells);
100✔
164
    }
100✔
165
}
166

167
fn neighbours(max_i: usize, max_j: usize, cells: &[Vec<Cell>]) -> Vec<[GridIdx; 8]> {
6✔
168
    let mut v = Vec::with_capacity((max_i + 1) * (max_j + 1));
6✔
169
    for (i, row) in cells.iter().enumerate() {
167✔
170
        for (j, _) in row.iter().enumerate() {
7,589✔
171
            let coord = Coord { i, j };
7,589✔
172
            v.push(neighbour_coords(max_i, max_j, &coord));
7,589✔
173
        }
7,589✔
174
    }
175
    v
6✔
176
}
6✔
177

178
fn neighbour_coords(max_i: usize, max_j: usize, coord: &Coord) -> [GridIdx; 8] {
7,592✔
179
    let width = max_j + 1;
7,592✔
180
    let Coord { i, j } = *coord;
7,592✔
181
    let to_grid_idx = |Coord { i, j }: Coord| GridIdx(width * i + j);
60,736✔
182

183
    let i_up = match i {
7,592✔
184
        0 => max_i,
74✔
185
        _ => i - 1,
7,518✔
186
    };
187

188
    let i_down = match i {
7,592✔
189
        _ if i == max_i => 0,
7,592✔
190
        _ => i + 1,
7,518✔
191
    };
192

193
    let j_left = match j {
7,592✔
194
        0 => max_j,
168✔
195
        _ => j - 1,
7,424✔
196
    };
197
    let j_right = match j {
7,592✔
198
        _ if j == max_j => 0,
7,592✔
199
        _ => j + 1,
7,424✔
200
    };
201

202
    let north = Coord { i: i_up, j };
7,592✔
203
    let north_east = Coord {
7,592✔
204
        i: i_up,
7,592✔
205
        j: j_right,
7,592✔
206
    };
7,592✔
207
    let east = Coord { i, j: j_right };
7,592✔
208
    let south_east = Coord {
7,592✔
209
        i: i_down,
7,592✔
210
        j: j_right,
7,592✔
211
    };
7,592✔
212
    let south = Coord { i: i_down, j };
7,592✔
213
    let south_west = Coord {
7,592✔
214
        i: i_down,
7,592✔
215
        j: j_left,
7,592✔
216
    };
7,592✔
217
    let west = Coord { i, j: j_left };
7,592✔
218
    let north_west = Coord { i: i_up, j: j_left };
7,592✔
219
    [
7,592✔
220
        to_grid_idx(north),
7,592✔
221
        to_grid_idx(north_east),
7,592✔
222
        to_grid_idx(east),
7,592✔
223
        to_grid_idx(south_east),
7,592✔
224
        to_grid_idx(south),
7,592✔
225
        to_grid_idx(south_west),
7,592✔
226
        to_grid_idx(west),
7,592✔
227
        to_grid_idx(north_west),
7,592✔
228
    ]
7,592✔
229
}
7,592✔
230

231
#[cfg(test)]
232
mod tests {
233
    use super::*;
234

235
    #[test]
1✔
236
    fn test_grid_new() {
1✔
237
        let grid = Grid::new(10, 5);
1✔
238
        assert_eq!(grid.cells().len(), 5);
1✔
239
        assert_eq!(grid.cells()[0].len(), 10);
1✔
240
    }
1✔
241

242
    #[test]
1✔
243
    fn test_neighbour_coords() {
1✔
244
        let grid = Grid::new(3, 3);
1✔
245
        let max_i = grid.max_i;
1✔
246
        let max_j = grid.max_j;
1✔
247
        /*
1✔
248
         * [ (0,0) (0,1) (0,2) ]
1✔
249
         * [ (1,0) (1,1) (1,2) ]
1✔
250
         * [ (2,0) (2,1) (2,2) ]
1✔
251
         */
1✔
252
        let n0 = neighbour_coords(max_i, max_j, &Coord { i: 0, j: 0 });
1✔
253
        assert_eq!(n0[0], grid.to_grid_idx(&Coord { i: 2, j: 0 }).unwrap()); // N
1✔
254
        assert_eq!(n0[1], grid.to_grid_idx(&Coord { i: 2, j: 1 }).unwrap()); // NE
1✔
255
        assert_eq!(n0[2], grid.to_grid_idx(&Coord { i: 0, j: 1 }).unwrap()); // E
1✔
256
        assert_eq!(n0[3], grid.to_grid_idx(&Coord { i: 1, j: 1 }).unwrap()); // SE
1✔
257
        assert_eq!(n0[4], grid.to_grid_idx(&Coord { i: 1, j: 0 }).unwrap()); // S
1✔
258
        assert_eq!(n0[5], grid.to_grid_idx(&Coord { i: 1, j: 2 }).unwrap()); // SW
1✔
259
        assert_eq!(n0[6], grid.to_grid_idx(&Coord { i: 0, j: 2 }).unwrap()); // W
1✔
260
        assert_eq!(n0[7], grid.to_grid_idx(&Coord { i: 2, j: 2 }).unwrap()); // NW
1✔
261
        let n1 = neighbour_coords(max_i, max_j, &Coord { i: 1, j: 1 });
1✔
262
        assert_eq!(n1[0], grid.to_grid_idx(&Coord { i: 0, j: 1 }).unwrap()); // N
1✔
263
        assert_eq!(n1[1], grid.to_grid_idx(&Coord { i: 0, j: 2 }).unwrap()); // NE
1✔
264
        assert_eq!(n1[2], grid.to_grid_idx(&Coord { i: 1, j: 2 }).unwrap()); // E
1✔
265
        assert_eq!(n1[3], grid.to_grid_idx(&Coord { i: 2, j: 2 }).unwrap()); // SE
1✔
266
        assert_eq!(n1[4], grid.to_grid_idx(&Coord { i: 2, j: 1 }).unwrap()); // S
1✔
267
        assert_eq!(n1[5], grid.to_grid_idx(&Coord { i: 2, j: 0 }).unwrap()); // SW
1✔
268
        assert_eq!(n1[6], grid.to_grid_idx(&Coord { i: 1, j: 0 }).unwrap()); // W
1✔
269
        assert_eq!(n1[7], grid.to_grid_idx(&Coord { i: 0, j: 0 }).unwrap()); // NW
1✔
270
        let n2 = neighbour_coords(max_i, max_j, &Coord { i: 2, j: 2 });
1✔
271
        assert_eq!(n2[0], grid.to_grid_idx(&Coord { i: 1, j: 2 }).unwrap()); // N
1✔
272
        assert_eq!(n2[1], grid.to_grid_idx(&Coord { i: 1, j: 0 }).unwrap()); // NE
1✔
273
        assert_eq!(n2[2], grid.to_grid_idx(&Coord { i: 2, j: 0 }).unwrap()); // E
1✔
274
        assert_eq!(n2[3], grid.to_grid_idx(&Coord { i: 0, j: 0 }).unwrap()); // SE
1✔
275
        assert_eq!(n2[4], grid.to_grid_idx(&Coord { i: 0, j: 2 }).unwrap()); // S
1✔
276
        assert_eq!(n2[5], grid.to_grid_idx(&Coord { i: 0, j: 1 }).unwrap()); // SW
1✔
277
        assert_eq!(n2[6], grid.to_grid_idx(&Coord { i: 2, j: 1 }).unwrap()); // W
1✔
278
        assert_eq!(n2[7], grid.to_grid_idx(&Coord { i: 1, j: 1 }).unwrap()); // NW
1✔
279
    }
1✔
280

281
    // Just a test to make sure advance can run for a large number of iterations
282
    #[test]
1✔
283
    fn test_advance() {
1✔
284
        let mut grid = Grid::new(50, 150);
1✔
285
        print!("{:?}", grid);
1✔
286
        for _ in 0..100 {
101✔
287
            grid.advance();
100✔
288
        }
100✔
289
    }
1✔
290

291
    #[test]
1✔
292
    fn test_alive_count() {
1✔
293
        let mut grid = Grid::new(3, 3);
1✔
294
        let new_cells = vec![
1✔
295
            vec![
1✔
296
                Cell(Status::Alive),
1✔
297
                Cell(Status::Alive),
1✔
298
                Cell(Status::Alive),
1✔
299
            ],
1✔
300
            vec![Cell(Status::Alive), Cell(Status::Dead), Cell(Status::Alive)],
1✔
301
            vec![
1✔
302
                Cell(Status::Alive),
1✔
303
                Cell(Status::Alive),
1✔
304
                Cell(Status::Alive),
1✔
305
            ],
1✔
306
        ]
1✔
307
        .into_iter()
1✔
308
        .flat_map(|v| v)
3✔
309
        .collect();
1✔
310
        grid.cells = new_cells;
1✔
311
        assert_eq!(alive_count(&grid), 8)
1✔
312
    }
1✔
313

314
    #[test]
1✔
315
    fn test_get_idx() {
1✔
316
        let mut grid = Grid::new(3, 3);
1✔
317
        let new_cells: Vec<Cell> = vec![
1✔
318
            vec![
1✔
319
                Cell(Status::Alive),
1✔
320
                Cell(Status::Alive),
1✔
321
                Cell(Status::Alive),
1✔
322
            ],
1✔
323
            vec![Cell(Status::Alive), Cell(Status::Dead), Cell(Status::Alive)],
1✔
324
            vec![
1✔
325
                Cell(Status::Alive),
1✔
326
                Cell(Status::Alive),
1✔
327
                Cell(Status::Alive),
1✔
328
            ],
1✔
329
        ]
1✔
330
        .into_iter()
1✔
331
        .flat_map(|v| v)
3✔
332
        .collect();
1✔
333
        grid.cells = new_cells;
1✔
334
        for idx in 0..9 {
10✔
335
            let cell = grid.get_idx(&GridIdx(idx)).unwrap();
9✔
336
            if idx != 4 {
9✔
337
                assert!(cell.alive())
8✔
338
            } else {
339
                assert!(!cell.alive())
1✔
340
            }
341
        }
342
    }
1✔
343

344
    /// Given
345
    ///
346
    /// [ (0,0) (0,1) (0,2) (0, 3) ]
347
    /// [ (1,0) (1,1) (1,2) (1, 3) ]
348
    /// [ (2,0) (2,1) (2,2) (2, 3) ]
349
    #[test]
1✔
350
    fn test_to_grid_idx() {
1✔
351
        let grid = Grid::new(4, 3);
1✔
352
        assert_eq!(grid.to_grid_idx(&Coord { i: 0, j: 0 }), Some(GridIdx(0)));
1✔
353
        assert_eq!(grid.to_grid_idx(&Coord { i: 1, j: 2 }), Some(GridIdx(6)));
1✔
354
        assert_eq!(grid.to_grid_idx(&Coord { i: 2, j: 3 }), Some(GridIdx(11)));
1✔
355
        assert_eq!(grid.to_grid_idx(&Coord { i: 3, j: 3 }), None);
1✔
356
    }
1✔
357

358
    fn alive_cells(grid: &Grid) -> Vec<Coord> {
1✔
359
        let mut v = vec![];
1✔
360
        for (i, row) in grid.cells().iter().enumerate() {
3✔
361
            for (j, cell) in row.iter().enumerate() {
9✔
362
                if cell.alive() {
9✔
363
                    let coord = Coord { i, j };
8✔
364
                    v.push(coord);
8✔
365
                }
8✔
366
            }
367
        }
368
        v
1✔
369
    }
1✔
370

371
    fn alive_count(grid: &Grid) -> usize {
1✔
372
        alive_cells(grid).len()
1✔
373
    }
1✔
374
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc