gol_engines/pattern.rs
1use crate::VERSION;
2use ahash::AHashMap as HashMap;
3use anyhow::{anyhow, Context, Result};
4use flate2::{
5 read::{GzDecoder, GzEncoder},
6 Compression,
7};
8use num_bigint::BigInt;
9use rand::{Rng, SeedableRng};
10use std::{fs, io::Read, path::Path};
11
12/// Nodes typically have size of about 32 bytes, so 2^32 (128 GiB) nodes
13/// is generally enough.
14type NodeIdx = u32;
15/// A type for measuring the depth of the quadtree.
16type SizeLog2 = u32;
17
18/// Represents a Game of Life pattern using a memory-efficient quadtree structure.
19///
20/// # Overview
21///
22/// This struct implements a quadtree where each node represents a square region
23/// of the pattern. Leaf nodes represent 8x8 cell blocks ([`PatternNode::Leaf`]),
24/// while internal nodes ([`PatternNode::Node`]) subdivide their region into four
25/// smaller quadrants (nw, ne, sw, se).
26///
27/// This structure allows for significant memory savings for large or sparse patterns
28/// through node deduplication (via `KIVMap`), making it ideal for compact
29/// checkpoints in Game of Life engines.
30///
31/// # Features
32///
33/// * **Quadtree Representation:** Efficiently stores patterns with regularities.
34/// Hovewer, even randomly-filled patterns are stored with insignificant overhead
35/// compared to a flat packed array.
36/// * **Power-of-2 Sizes:** Supports square patterns with side lengths that are
37/// powers of 2 (e.g., 1x1, 2x2, 8x8, 16x16...).
38/// * **Serialization:** Can be serialized to and deserialized from various
39/// standard formats (see [`PatternFormat`]):
40/// * `PackedCells`
41/// * `Macrocell`
42/// * `CompressedMacrocell`
43/// * `RLE`
44/// * **Precise Population Count:** Accurately computes the total number of
45/// live cells in the pattern using arbitrary-precision arithmetic. This method
46/// guarantees precise results even for extremely large patterns where cell counts
47/// might exceed the limits of fixed-size integers.
48/// * **Metafy Expansion:** Recursively expands the pattern by replacing each cell
49/// with the corresponding base metacell from the provided `metacells`. This approach
50/// delivers higher performance than Golly scripts and enables efficient creation
51/// of multi-level metapatterns.
52///
53/// # Format Recommendations
54///
55/// For best performance and memory efficiency, prefer [`PatternFormat::Macrocell`] or
56/// [`PatternFormat::CompressedMacrocell`] over other formats. These formats:
57///
58/// * Best align with the internal quadtree representation used by `Pattern`
59/// * Provide faster loading and saving for large patterns
60/// * Handle sparse patterns more efficiently
61/// * Support patterns of virtually unlimited size
62///
63/// If you encounter issues loading patterns that work in Golly and should work here,
64/// the file might not conform to the format specifications detailed at
65/// https://golly.sourceforge.io/Help/formats.html.
66/// Try opening the problematic pattern in a modern version of Golly and saving it again
67/// to ensure it meets the expected format requirements.
68///
69/// # Limitations
70///
71/// * **Two-State Only:** Currently supports only standard two-state
72/// (dead/alive) cells.
73/// * **B3/S23 Rule:** Assumes Conway's standard B3/S23 rule for format conversions
74/// (like RLE and Macrocell) and engine operations.
75/// * **Size Limitations:** While the theoretical maximum dimension is about
76/// `2^(2^32) x 2^(2^32)` (limited by `SizeLog2`), the practical size is
77/// constrained by the `NodeIdx` limit of `2^32` unique nodes.
78/// Dense or random patterns may exhaust node indices much sooner;
79/// the maximum size guaranteed to fit regardless of pattern content is
80/// `2^18 x 2^18`. Highly repetitive patterns can achieve much larger sizes.
81/// * **Memory Allocation:** Relies on Rust's default allocator. If memory allocation
82/// fails (e.g., due to insufficient system memory), functions will typically
83/// panic and terminate rather than return an error.
84#[derive(Clone)]
85pub struct Pattern {
86 /// The root node of the quadtree, has size of
87 /// `(1 << size_log2) x (1 << size_log2)`.
88 root: NodeIdx,
89 /// `size_log2` is the log2(side length of the square).
90 /// For example, a 16x16 pattern has `size_log2` of 4.
91 size_log2: SizeLog2,
92 /// Storage for the quadtree nodes.
93 kiv: KIVMap,
94 /// Caches blank nodes for faster search.
95 blank_nodes: Vec<NodeIdx>,
96}
97
98impl Pattern {
99 /// Returns the root node index of the pattern.
100 ///
101 /// The root node represents the top-level quadtree node covering the entire pattern.
102 pub fn get_root(&self) -> NodeIdx {
103 self.root
104 }
105
106 /// Changes the root node of the pattern to a new node with a specified size.
107 ///
108 /// # Safety
109 ///
110 /// This function is unsafe because it requires the caller to ensure:
111 /// - `root` is a valid node index that exists in the pattern's KIVMap
112 /// - `size_log2` correctly represents the size of the new root node
113 ///
114 /// # Arguments
115 ///
116 /// * `root` - The index of the new root node
117 /// * `size_log2` - The log base 2 of the new root's side length
118 pub unsafe fn change_root(&mut self, root: NodeIdx, size_log2: SizeLog2) {
119 self.root = root;
120 self.size_log2 = size_log2;
121 }
122
123 /// Finds an existing node with the given content or creates a new one.
124 ///
125 /// This method delegates to the internal KIVMap to find or create a node,
126 /// maintaining the pattern's node deduplication property.
127 ///
128 /// # Arguments
129 ///
130 /// * `node` - The pattern node content to find or create
131 ///
132 /// # Returns
133 ///
134 /// A `NodeIdx` that uniquely identifies the node with the specified content
135 pub fn find_or_create_node(&mut self, node: PatternNode) -> NodeIdx {
136 self.kiv.find_or_create_node(node)
137 }
138
139 /// Returns the log base 2 of the pattern's side length.
140 ///
141 /// The pattern is always square with side length of `2^size_log2`.
142 /// For example, a pattern with `size_log2 = 3` is 8x8 cells.
143 pub fn get_size_log2(&self) -> SizeLog2 {
144 self.size_log2
145 }
146
147 /// Retrieves a node from the pattern by its index.
148 ///
149 /// # Arguments
150 ///
151 /// * `idx` - The index of the node to retrieve.
152 ///
153 /// # Returns
154 ///
155 /// A reference to the requested `PatternNode`.
156 pub fn get_node(&self, idx: NodeIdx) -> &PatternNode {
157 self.kiv.get_node(idx)
158 }
159
160 /// Computes a 64-bit hash of the entire pattern. Intended for fast
161 /// probabilistic pattern comparison.
162 ///
163 /// This method recursively traverses the quadtree and combines hashes
164 /// of each node using a non-cryptographic hash function. The hash is
165 /// consistent for identical patterns regardless of their internal
166 /// representation.
167 ///
168 /// # Returns
169 ///
170 /// A 64-bit hash value for the pattern.
171 pub fn hash(&self) -> u64 {
172 fn inner(idx: NodeIdx, cache: &mut HashMap<NodeIdx, u64>, kiv: &KIVMap) -> u64 {
173 if let Some(&val) = cache.get(&idx) {
174 return val;
175 }
176
177 let combine = |x: u64, y: u64| -> u64 {
178 x ^ y
179 .wrapping_add(0x9e3779b9)
180 .wrapping_add(x << 6)
181 .wrapping_add(x >> 2)
182 };
183
184 match kiv.get_node(idx) {
185 PatternNode::Leaf(cells) => *cells,
186 PatternNode::Node { nw, ne, sw, se } => {
187 let mut result = 0;
188 for x in [*nw, *ne, *sw, *se] {
189 result = combine(result, inner(x, cache, kiv));
190 }
191 cache.insert(idx, result);
192 result
193 }
194 }
195 }
196
197 let mut cache = HashMap::new();
198 inner(self.root, &mut cache, &self.kiv)
199 }
200
201 /// Counts the total number of alive cells in the pattern.
202 ///
203 /// This method efficiently traverses the quadtree structure and
204 /// memoizes intermediate results to avoid redundant calculations.
205 ///
206 /// # Returns
207 ///
208 /// A `BigInt` representing the total number of alive cells, which can
209 /// be arbitrarily large for very large patterns.
210 pub fn population(&self) -> BigInt {
211 fn inner<'a>(idx: NodeIdx, cache: &'a mut HashMap<NodeIdx, BigInt>, kiv: &'a KIVMap) {
212 let result = match kiv.get_node(idx) {
213 PatternNode::Leaf(cells) => BigInt::from(cells.count_ones()),
214 PatternNode::Node { nw, ne, sw, se } => {
215 let mut result = BigInt::ZERO;
216 for x in [*nw, *ne, *sw, *se] {
217 if !cache.contains_key(&x) {
218 inner(x, cache, kiv);
219 }
220 result += cache.get(&x).unwrap();
221 }
222 result
223 }
224 };
225 cache.insert(idx, result);
226 }
227
228 let mut cache = HashMap::new();
229 inner(self.root, &mut cache, &self.kiv);
230 cache.remove(&self.root).unwrap()
231 }
232
233 /// Expands the pattern to a larger size by adding dead cells to the right and bottom.
234 ///
235 /// This function increases the `size_log2` of the pattern to the requested value, effectively
236 /// making the pattern's dimensions larger. The original pattern content remains in the top-left
237 /// corner, with dead cells filling the newly added space.
238 ///
239 /// # Arguments
240 ///
241 /// * `size_log2` - The desired log base 2 of the new pattern size.
242 ///
243 /// # Examples
244 ///
245 /// ```rust
246 /// use gol_engines::{Pattern, PatternFormat};
247 ///
248 /// let mut pattern = gol_engines::Pattern::random(2, None).unwrap();
249 /// assert_eq!(pattern.get_size_log2(), 2); // 4x4 pattern
250 ///
251 /// // Expand to 256x256
252 /// pattern.expand(8);
253 /// assert_eq!(pattern.get_size_log2(), 8);
254 /// ```
255 ///
256 /// # Note
257 ///
258 /// If `size_log2` is less than or equal to the current size, this function does nothing.
259 pub fn expand(&mut self, size_log2: SizeLog2) {
260 if self.size_log2 >= size_log2 {
261 return;
262 }
263 if size_log2 <= 3 {
264 self.size_log2 = size_log2;
265 return;
266 }
267 self.size_log2 = self.size_log2.max(3);
268 while self.size_log2 < size_log2 {
269 let blank = Self::find_or_create_blank_node(
270 self.size_log2,
271 &mut self.kiv,
272 &mut self.blank_nodes,
273 );
274
275 self.root = self.kiv.find_or_create_node(PatternNode::Node {
276 nw: self.root,
277 ne: blank,
278 sw: blank,
279 se: blank,
280 });
281 self.size_log2 += 1;
282 }
283 }
284
285 /// Creates a random pattern of the specified size.
286 ///
287 /// # Arguments
288 ///
289 /// * `size_log2` - Log base 2 of the pattern's side length.
290 /// * `seed` - Optional seed for the random number generator.
291 /// If None, seeds from the OS.
292 ///
293 /// # Returns
294 ///
295 /// A `Result` containing either the created random `Pattern` or an error.
296 ///
297 /// # Errors
298 ///
299 /// Returns an error if `size_log2` is too large (would cause overflow).
300 pub fn random(size_log2: SizeLog2, seed: Option<u64>) -> Result<Self> {
301 if 1usize.checked_shl(size_log2 * 2).is_none() {
302 return Err(anyhow!("size_log2 {} is too large", size_log2));
303 }
304 let n = 1usize << size_log2;
305 let mut cells = vec![0u8; (n * n).div_ceil(8).max(n)];
306 if let Some(x) = seed {
307 rand_chacha::ChaCha8Rng::seed_from_u64(x)
308 } else {
309 rand_chacha::ChaCha8Rng::from_os_rng()
310 }
311 .fill(&mut cells[..]);
312 if size_log2 < 3 {
313 // clear the upper bits
314 for x in cells.iter_mut() {
315 *x &= ((1u32 << n) - 1) as u8;
316 }
317 }
318 Self::from_packed_cells(&cells)
319 }
320
321 /// Recursively expands a pattern by replacing cells with `metacells` multiple times.
322 /// Optimized for building huge multi-level metacells.
323 ///
324 /// This function treats the current pattern (`self`) as a high-level layout.
325 /// It performs `level` steps of expansion. In each step, every 'alive' cell
326 /// is replaced by the `metacells[1]` pattern (ON state), and every 'dead' cell
327 /// is replaced by the `metacells[0]` pattern (OFF state).
328 ///
329 /// For example, if `level = 1`, each cell in `self` is directly replaced by the
330 /// corresponding pattern from `metacells`. If `level = 2`, each cell in `self` is
331 /// replaced by a meta-pattern, which itself is constructed by replacing *its*
332 /// cells with the patterns from `metacells`.
333 ///
334 /// # Arguments
335 ///
336 /// * `self` - The pattern representing the initial high-level layout.
337 /// * `metacells` - An array containing two `Pattern`s defining the base unit cells:
338 /// `metacells[0]` for the OFF state and `metacells[1]` for the ON state.
339 /// These patterns must have the same `size_log2`.
340 /// * `level` - The number of recursive expansion level to perform.
341 /// Zero means doing nothing.
342 ///
343 /// # Returns
344 ///
345 /// A `Result` containing the expanded `Pattern`. The final size exponent will be
346 /// `self.size_log2 + level * base_size_log2`, where `base_size_log2` is the
347 /// `size_log2` of the patterns in `metacells`.
348 ///
349 /// # Errors
350 ///
351 /// Returns an error if:
352 /// - `metacells[0].size_log2` does not equal `metacells[1].size_log2`
353 /// - metacells are smaller than 8x8
354 pub fn metafy(&self, metacells: [&Pattern; 2], level: u32) -> Result<Self> {
355 if metacells[0].size_log2 != metacells[1].size_log2 {
356 return Err(anyhow!(
357 "Metacells must have the same size_log2: {} != {}",
358 metacells[0].size_log2,
359 metacells[1].size_log2
360 ));
361 }
362 if metacells[0].size_log2 < 3 {
363 return Err(anyhow!(
364 "Metacell size_log2 must be at least 3: {}",
365 metacells[0].size_log2
366 ));
367 }
368
369 if level == 0 {
370 return Ok(self.clone());
371 }
372
373 fn copy_from_pattern(
374 idx: NodeIdx,
375 pattern: &Pattern,
376 kiv: &mut KIVMap,
377 cache: &mut HashMap<NodeIdx, NodeIdx>,
378 ) -> NodeIdx {
379 if let Some(&x) = cache.get(&idx) {
380 return x;
381 }
382 let result = match pattern.get_node(idx) {
383 PatternNode::Leaf(cells) => kiv.find_or_create_node(PatternNode::Leaf(*cells)),
384 PatternNode::Node { nw, ne, sw, se } => {
385 let nw = copy_from_pattern(*nw, pattern, kiv, cache);
386 let ne = copy_from_pattern(*ne, pattern, kiv, cache);
387 let sw = copy_from_pattern(*sw, pattern, kiv, cache);
388 let se = copy_from_pattern(*se, pattern, kiv, cache);
389 kiv.find_or_create_node(PatternNode::Node { nw, ne, sw, se })
390 }
391 };
392 cache.insert(idx, result);
393 result
394 }
395
396 let mut kiv = KIVMap::new();
397 let mut cache = HashMap::new();
398 let mut metacell_idx = [0; 2];
399 for (src, dst) in metacells.iter().zip(metacell_idx.iter_mut()) {
400 *dst = copy_from_pattern(src.root, src, &mut kiv, &mut cache);
401 cache.clear();
402 }
403
404 fn build_pattern_from_metacells(
405 pattern: &Pattern,
406 metacells: [NodeIdx; 2],
407 idx: NodeIdx,
408 kiv: &mut KIVMap,
409 cache: &mut HashMap<NodeIdx, NodeIdx>,
410 ) -> NodeIdx {
411 if let Some(&x) = cache.get(&idx) {
412 return x;
413 }
414 let result = match pattern.get_node(idx) {
415 PatternNode::Leaf(cells) => {
416 let mut a = [[0; 8]; 8];
417 for y in 0..8 {
418 for x in 0..8 {
419 a[y][x] = metacells[(cells >> (x + y * 8)) as usize & 1];
420 }
421 }
422 if pattern.size_log2 == 0 {
423 return a[0][0];
424 }
425 let mut b = [[0; 4]; 4];
426 for y in 0..4 {
427 for x in 0..4 {
428 b[y][x] = kiv.find_or_create_node(PatternNode::Node {
429 nw: a[y * 2][x * 2],
430 ne: a[y * 2][x * 2 + 1],
431 sw: a[y * 2 + 1][x * 2],
432 se: a[y * 2 + 1][x * 2 + 1],
433 });
434 }
435 }
436 if pattern.size_log2 == 1 {
437 return b[0][0];
438 }
439 let mut c = [[0; 2]; 2];
440 for y in 0..2 {
441 for x in 0..2 {
442 c[y][x] = kiv.find_or_create_node(PatternNode::Node {
443 nw: b[y * 2][x * 2],
444 ne: b[y * 2][x * 2 + 1],
445 sw: b[y * 2 + 1][x * 2],
446 se: b[y * 2 + 1][x * 2 + 1],
447 });
448 }
449 }
450 if pattern.size_log2 == 2 {
451 return c[0][0];
452 }
453 kiv.find_or_create_node(PatternNode::Node {
454 nw: c[0][0],
455 ne: c[0][1],
456 sw: c[1][0],
457 se: c[1][1],
458 })
459 }
460 PatternNode::Node { nw, ne, sw, se } => {
461 let nw = build_pattern_from_metacells(pattern, metacells, *nw, kiv, cache);
462 let ne = build_pattern_from_metacells(pattern, metacells, *ne, kiv, cache);
463 let sw = build_pattern_from_metacells(pattern, metacells, *sw, kiv, cache);
464 let se = build_pattern_from_metacells(pattern, metacells, *se, kiv, cache);
465 kiv.find_or_create_node(PatternNode::Node { nw, ne, sw, se })
466 }
467 };
468 cache.insert(idx, result);
469 result
470 }
471
472 for _ in 0..level - 1 {
473 metacell_idx = [0, 1].map(|i| {
474 let result = build_pattern_from_metacells(
475 metacells[i],
476 metacell_idx,
477 metacells[i].root,
478 &mut kiv,
479 &mut cache,
480 );
481 cache.clear();
482 result
483 });
484 }
485
486 let root =
487 build_pattern_from_metacells(self, metacell_idx, self.root, &mut kiv, &mut cache);
488 Ok(Self {
489 root,
490 size_log2: self.size_log2 + level * metacells[0].size_log2,
491 kiv,
492 blank_nodes: vec![],
493 })
494 }
495
496 /// Creates a pattern from data in the specified format.
497 ///
498 /// # Arguments
499 ///
500 /// * `format` - The format of the provided data.
501 /// * `data` - The bytes containing the pattern data.
502 ///
503 /// # Returns
504 ///
505 /// A `Result` containing either the created `Pattern` or an error.
506 ///
507 /// # Errors
508 ///
509 /// Returns format-specific errors:
510 /// - `PackedCells`: If the data does not represent a square with a side that is a power of 2
511 /// - `Macrocell` or `RLE`: If data is invalid, uses more than two states, or specifies
512 /// non-B3/S23 rules
513 /// - `CompressedMacrocell`: If decompression fails or macrocell parsing errors occur
514 ///
515 /// # Performance Considerations
516 ///
517 /// For large or highly non-square patterns, the tree-based formats (`Macrocell` and
518 /// `CompressedMacrocell`) generally provide better performance and memory efficiency than
519 /// the flat array-based formats (`PackedCells` and `RLE`).
520 pub fn from_format(format: PatternFormat, data: &[u8]) -> Result<Self> {
521 match format {
522 PatternFormat::PackedCells => Self::from_packed_cells(data),
523 PatternFormat::Macrocell => Self::from_macrocell(data),
524 PatternFormat::CompressedMacrocell => Self::from_compressed_macrocell(data),
525 PatternFormat::RLE => Self::from_rle(data),
526 }
527 }
528
529 /// Converts the pattern to the specified format.
530 ///
531 /// # Arguments
532 ///
533 /// * `format` - The desired output format.
534 ///
535 /// # Returns
536 ///
537 /// A `Result` containing either the bytes of the pattern in the requested format
538 /// or an error.
539 ///
540 /// # Errors
541 ///
542 /// Returns format-specific errors:
543 /// - `PackedCells` or `RLE`: If `size_log2` is too large (would cause overflow)
544 /// - `Macrocell` or `CompressedMacrocell`: If the pattern is blank (contains no alive cells)
545 pub fn to_format(&self, format: PatternFormat) -> Result<Vec<u8>> {
546 match format {
547 PatternFormat::PackedCells => self.to_packed_cells(),
548 PatternFormat::Macrocell => self.to_macrocell(),
549 PatternFormat::CompressedMacrocell => self.to_compressed_macrocell(),
550 PatternFormat::RLE => self.to_rle(),
551 }
552 }
553
554 /// Creates a pattern from a file.
555 ///
556 /// The format will be inferred from the file extension:
557 /// - `.mc` → `PatternFormat::Macrocell`
558 /// - `.mc.gz` → `PatternFormat::CompressedMacrocell`
559 /// - `.rle` → `PatternFormat::RLE`
560 ///
561 /// # Arguments
562 ///
563 /// * `path` - Path to the file containing the pattern data.
564 ///
565 /// # Returns
566 ///
567 /// A `Result` containing either the created `Pattern` or an error.
568 ///
569 /// # Errors
570 ///
571 /// Returns an error if:
572 /// - File cannot be read
573 /// - File extension is not recognized
574 /// - Format-specific errors occur from `from_format`
575 pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self> {
576 let path = path.as_ref();
577 let data = fs::read(path).with_context(|| format!("Failed to read file: {:?}", path))?;
578
579 if path.extension() == Some("mc".as_ref()) {
580 return Self::from_format(PatternFormat::Macrocell, &data);
581 }
582
583 if path
584 .to_str()
585 .map(|s| s.ends_with(".mc.gz"))
586 .unwrap_or(false)
587 {
588 return Self::from_format(PatternFormat::CompressedMacrocell, &data);
589 }
590
591 if path.extension() == Some("rle".as_ref()) {
592 return Self::from_format(PatternFormat::RLE, &data);
593 }
594
595 Err(anyhow!("Cannot infer format from file path: {:?}", path))
596 }
597
598 /// Saves the pattern to a file.
599 ///
600 /// The format will be inferred from the file extension:
601 /// - `.rle` → `PatternFormat::RLE`
602 /// - `.mc` → `PatternFormat::Macrocell`
603 /// - `.mc.gz` → `PatternFormat::CompressedMacrocell`
604 ///
605 /// The file will be created or overwritten.
606 ///
607 /// # Arguments
608 ///
609 /// * `path` - Path where the pattern will be saved.
610 ///
611 /// # Returns
612 ///
613 /// A `Result<()>` indicating success or containing an error.
614 ///
615 /// # Errors
616 ///
617 /// Returns an error if:
618 /// - File cannot be written
619 /// - File extension is not recognized
620 /// - Format-specific errors from `to_format`
621 pub fn to_file<P: AsRef<Path>>(&self, path: P) -> Result<()> {
622 let path = path.as_ref();
623
624 let format = if path.extension() == Some("mc".as_ref()) {
625 PatternFormat::Macrocell
626 } else if path
627 .to_str()
628 .map(|s| s.ends_with(".mc.gz"))
629 .unwrap_or(false)
630 {
631 PatternFormat::CompressedMacrocell
632 } else if path.extension() == Some("rle".as_ref()) {
633 PatternFormat::RLE
634 } else {
635 return Err(anyhow!("Cannot infer format from file path: {:?}", path));
636 };
637
638 let data = self.to_format(format)?;
639 fs::write(path, data).with_context(|| format!("Failed to write file: {:?}", path))
640 }
641
642 /// Creates a pattern from packed cell data. See [`PatternFormat::PackedCells`].
643 ///
644 /// # Arguments
645 ///
646 /// * `data` - Bytes containing the packed cell data.
647 ///
648 /// # Returns
649 ///
650 /// A `Result` containing either the created `Pattern` or an error.
651 ///
652 /// # Errors
653 ///
654 /// Returns an error if the data does not represent a square with a side that is a power of 2.
655 fn from_packed_cells(data: &[u8]) -> Result<Self> {
656 if data.is_empty() {
657 return Err(anyhow!("Packed cells are empty"));
658 }
659 let size_log2 = data.len().ilog2().min((data.len().ilog2() + 3) / 2);
660 let n = 1usize << size_log2;
661 if data.len() != (n * n).div_ceil(8).max(n) {
662 return Err(anyhow!(
663 "Packed cells don't represent a square with a power of 2 side"
664 ));
665 }
666
667 if size_log2 < 3 {
668 for x in data.iter() {
669 if *x & !((1u32 << n) - 1) as u8 != 0 {
670 return Err(anyhow!("Found cells out of bounds"));
671 }
672 }
673 }
674
675 let mut kiv = KIVMap::new();
676 let (mut nodes_curr, mut nodes_next) = (vec![], vec![]);
677
678 let stride = n.max(8);
679 for y in (0..n).step_by(8) {
680 for x in (0..n).step_by(8) {
681 let mut leaf = [0; 8];
682 for dy in 0..8.min(n) {
683 leaf[dy] = data[(x + (dy + y) * stride) / 8];
684 }
685 nodes_curr
686 .push(kiv.find_or_create_node(PatternNode::Leaf(u64::from_le_bytes(leaf))));
687 }
688 }
689 let mut t = stride / 8;
690 while t != 1 {
691 for y in (0..t).step_by(2) {
692 for x in (0..t).step_by(2) {
693 let nw = nodes_curr[x + y * t];
694 let ne = nodes_curr[(x + 1) + y * t];
695 let sw = nodes_curr[x + (y + 1) * t];
696 let se = nodes_curr[(x + 1) + (y + 1) * t];
697 nodes_next.push(kiv.find_or_create_node(PatternNode::Node { nw, ne, sw, se }));
698 }
699 }
700 std::mem::swap(&mut nodes_curr, &mut nodes_next);
701 nodes_next.clear();
702 t >>= 1;
703 }
704 assert_eq!(nodes_curr.len(), 1);
705 Ok(Self {
706 root: nodes_curr[0],
707 size_log2,
708 kiv,
709 blank_nodes: vec![],
710 })
711 }
712
713 /// Converts the pattern to packed cell data. See [`PatternFormat::PackedCells`].
714 ///
715 /// # Returns
716 ///
717 /// A `Result` containing either the bytes of the packed cells representation
718 /// or an error.
719 ///
720 /// # Errors
721 ///
722 /// Returns an error if `self.size_log2` is too large (would cause overflow).
723 fn to_packed_cells(&self) -> Result<Vec<u8>> {
724 fn inner(
725 this: &Pattern,
726 idx: NodeIdx,
727 size_log2: SizeLog2,
728 cells: &mut Vec<u8>,
729 stride: usize,
730 x: usize,
731 y: usize,
732 ) {
733 match this.get_node(idx) {
734 PatternNode::Leaf(cells_data) => {
735 for dy in 0..1 << size_log2 {
736 cells[(x + (dy + y) * stride) / 8] |= cells_data.to_le_bytes()[dy];
737 }
738 }
739 PatternNode::Node { nw, ne, sw, se } => {
740 let half = 1 << (size_log2 - 1);
741 inner(this, *nw, size_log2 - 1, cells, stride, x, y);
742 inner(this, *ne, size_log2 - 1, cells, stride, x + half, y);
743 inner(this, *sw, size_log2 - 1, cells, stride, x, y + half);
744 inner(this, *se, size_log2 - 1, cells, stride, x + half, y + half);
745 }
746 }
747 }
748
749 if 1usize.checked_shl(self.size_log2 * 2).is_none() {
750 return Err(anyhow!("size_log2 {} is too large", self.size_log2));
751 }
752 let n = 1usize << self.size_log2;
753 let mut cells = vec![0; (n * n).div_ceil(8).max(n)];
754 inner(self, self.root, self.size_log2, &mut cells, n.max(8), 0, 0);
755 Ok(cells)
756 }
757
758 /// Creates a pattern from data in the macrocell format. See [`PatternFormat::Macrocell`].
759 ///
760 /// # Arguments
761 ///
762 /// * `data` - The bytes containing the macrocell data.
763 ///
764 /// # Returns
765 ///
766 /// A `Result` containing either the created `Pattern` or an error.
767 ///
768 /// # Errors
769 ///
770 /// Returns an error if:
771 /// - Data is invalid
772 /// - Algorithm has more than two states
773 /// - A rule other than B3/S23 is specified
774 fn from_macrocell(data: &[u8]) -> Result<Self> {
775 let mut kiv = KIVMap::new();
776 let mut blank_nodes = vec![];
777 let mut codes_and_sizes = HashMap::<u32, (NodeIdx, SizeLog2)>::new();
778 let mut last_code_and_size = None;
779
780 let mut lines = data
781 .split(|&x| x == b'\n')
782 .map(|x| x.strip_suffix(b"\r").unwrap_or(x))
783 .filter(|x| !x.is_empty());
784 if !lines
785 .next()
786 .ok_or_else(|| anyhow!("Missing format identifier"))?
787 .starts_with(b"[M2]")
788 {
789 return Err(anyhow!("Invalid format identifier"));
790 }
791
792 for s in lines {
793 if s[0] == b'#' {
794 if s.starts_with(b"#R") && &s[2..] != b" B3/S23" {
795 return Err(anyhow!("Only B3/S23 rule is supported"));
796 }
797 continue;
798 }
799
800 let idx = (codes_and_sizes.len() + 1).try_into().with_context(|| {
801 format!("Failed to convert {} to NodeIdx", codes_and_sizes.len() + 1)
802 })?;
803
804 let code_and_size = if s[0].is_ascii_digit() {
805 // non-leaf
806 let numbers: Vec<u32> = s
807 .split(|&x| x == b' ')
808 .map(|part| {
809 std::str::from_utf8(part)
810 .with_context(|| format!("Invalid UTF-8 sequence in {part:?}"))
811 .and_then(|s| {
812 s.parse::<u32>()
813 .with_context(|| format!("Failed to parse integer from '{s}'"))
814 })
815 })
816 .collect::<Result<_>>()?;
817 if numbers.len() != 5 {
818 return Err(anyhow!("Expected 5 numbers, got {}", numbers.len()));
819 }
820 if numbers[0] < 4 {
821 return Err(anyhow!(
822 "Node {} has size_log2 {}, expected >= 4",
823 idx,
824 numbers[0],
825 ));
826 }
827
828 let mut resolve = |x: u32| -> Result<NodeIdx> {
829 if x == 0 {
830 Ok(Self::find_or_create_blank_node(
831 numbers[0] - 1,
832 &mut kiv,
833 &mut blank_nodes,
834 ))
835 } else {
836 let (code, size_log2) =
837 codes_and_sizes.get(&x).copied().ok_or_else(|| {
838 anyhow!("Reference to undeclared node with code {}", x)
839 })?;
840 if size_log2 != numbers[0] - 1 {
841 return Err(anyhow!(
842 "Node {} has size_log2 {}, expected {}",
843 x,
844 size_log2,
845 numbers[0] - 1
846 ));
847 }
848 Ok(code)
849 }
850 };
851
852 let nw = resolve(numbers[1])?;
853 let ne = resolve(numbers[2])?;
854 let sw = resolve(numbers[3])?;
855 let se = resolve(numbers[4])?;
856 let code = kiv.find_or_create_node(PatternNode::Node { nw, ne, sw, se });
857 (code, numbers[0])
858 } else {
859 // is leaf
860 let mut cells = 0u64;
861 let (mut i, mut j) = (0, 0);
862 for &c in s {
863 match c {
864 b'$' => (i, j) = (i + 1, 0),
865 b'*' => {
866 if i >= 8 || j >= 8 {
867 return Err(anyhow!(
868 "Leaf {} does not fit in 8x8",
869 String::from_utf8_lossy(s)
870 ));
871 }
872 cells |= 1 << (i * 8 + j);
873 j += 1;
874 }
875 b'.' => {
876 j += 1;
877 }
878 _ => {
879 return Err(anyhow!(
880 "Invalid symbol '{}' in leaf {}",
881 c as char,
882 String::from_utf8_lossy(s)
883 ))
884 }
885 }
886 }
887 let code = kiv.find_or_create_node(PatternNode::Leaf(cells));
888 (code, 3)
889 };
890 last_code_and_size = Some(code_and_size);
891 codes_and_sizes.insert(idx, code_and_size);
892 }
893 let (root, size_log2) =
894 last_code_and_size.ok_or_else(|| anyhow!("No nodes found in the pattern"))?;
895 Ok(Self {
896 root,
897 size_log2,
898 kiv,
899 blank_nodes,
900 })
901 }
902
903 /// Converts the pattern to the macrocell format. See [`PatternFormat::Macrocell`].
904 ///
905 /// # Returns
906 ///
907 /// A `Result` containing either the bytes of the macrocell representation
908 /// or an error.
909 ///
910 /// # Errors
911 ///
912 /// Returns an error if the pattern is blank (contains no alive cells).
913 fn to_macrocell(&self) -> Result<Vec<u8>> {
914 fn inner(
915 this: &Pattern,
916 idx: NodeIdx,
917 size_log2: SizeLog2,
918 codes: &mut HashMap<NodeIdx, usize>,
919 blank_nodes: &mut Vec<NodeIdx>,
920 result: &mut Vec<u8>,
921 ) -> usize {
922 // if already serialized
923 if let Some(&x) = codes.get(&idx) {
924 return x;
925 }
926 // if node is blank
927 if let Some(x) = Pattern::find_blank_node(size_log2, &this.kiv, blank_nodes) {
928 if x == idx {
929 return 0;
930 }
931 }
932
933 match *this.get_node(idx) {
934 PatternNode::Leaf(x) => {
935 for t in x.to_le_bytes() {
936 for i in 0..8 {
937 if (t >> i) & 1 != 0 {
938 result.push(b'*');
939 } else {
940 result.push(b'.');
941 }
942 }
943 while result.ends_with(b".") {
944 result.pop();
945 }
946 result.push(b'$');
947 }
948 while result.ends_with(b"$$") {
949 result.pop();
950 }
951 result.push(b'\n');
952 }
953 PatternNode::Node { nw, ne, sw, se } => {
954 let new_line = format!(
955 "{} {} {} {} {}\n",
956 size_log2,
957 inner(this, nw, size_log2 - 1, codes, blank_nodes, result),
958 inner(this, ne, size_log2 - 1, codes, blank_nodes, result),
959 inner(this, sw, size_log2 - 1, codes, blank_nodes, result),
960 inner(this, se, size_log2 - 1, codes, blank_nodes, result),
961 );
962 result.extend_from_slice(new_line.as_bytes());
963 }
964 }
965 codes.insert(idx, codes.len() + 1);
966 codes.len()
967 }
968
969 let mut blank_nodes = self.blank_nodes.clone();
970 if let Some(x) = Self::find_blank_node(self.size_log2, &self.kiv, &mut blank_nodes) {
971 if x == self.root {
972 return Err(anyhow!("Cannot serialize blank pattern"));
973 }
974 }
975
976 let mut codes = HashMap::new();
977 let mut result = format!("[M2] (gol_engines {VERSION})\n#R B3/S23\n").into_bytes();
978 inner(
979 self,
980 self.root,
981 self.size_log2,
982 &mut codes,
983 &mut blank_nodes,
984 &mut result,
985 );
986
987 Ok(result)
988 }
989
990 /// Creates a pattern from gzip-compressed macrocell data.
991 /// See [`PatternFormat::CompressedMacrocell`].
992 ///
993 /// This method decompresses the input data and then parses it using
994 /// the standard macrocell format parser.
995 ///
996 /// # Arguments
997 ///
998 /// * `compressed_data` - The gzip-compressed bytes containing macrocell data.
999 ///
1000 /// # Returns
1001 ///
1002 /// A `Result` containing either the created `Pattern` or an error.
1003 ///
1004 /// # Errors
1005 ///
1006 /// Returns an error if:
1007 /// - The input data is not valid gzip-compressed data
1008 /// - Other errors from `from_macrocell` occur
1009 fn from_compressed_macrocell(compressed_data: &[u8]) -> Result<Self> {
1010 // Create a gzip decoder
1011 let mut decoder = GzDecoder::new(compressed_data);
1012 let mut decompressed_data = Vec::new();
1013 decoder
1014 .read_to_end(&mut decompressed_data)
1015 .context("Failed to decompress macrocell data")?;
1016 Self::from_macrocell(&decompressed_data)
1017 }
1018
1019 /// Converts the pattern to gzip-compressed macrocell format.
1020 /// See [`PatternFormat::CompressedMacrocell`].
1021 ///
1022 /// This method first converts the pattern to macrocell format using
1023 /// `to_macrocell`, then compresses the result using gzip.
1024 ///
1025 /// # Returns
1026 ///
1027 /// A `Result` containing either the gzip-compressed bytes of the
1028 /// macrocell representation or an error.
1029 ///
1030 /// # Errors
1031 ///
1032 /// Returns an error if:
1033 /// - The pattern is blank (contains no alive cells)
1034 /// - Compression fails
1035 fn to_compressed_macrocell(&self) -> Result<Vec<u8>> {
1036 let macrocell_data = self.to_macrocell()?;
1037 let mut encoder = GzEncoder::new(¯ocell_data[..], Compression::default());
1038 let mut compressed_data = Vec::new();
1039 encoder
1040 .read_to_end(&mut compressed_data)
1041 .context("Failed to compress macrocell data")?;
1042 Ok(compressed_data)
1043 }
1044
1045 /// Creates a pattern from data in the Extended RLE format. See [`PatternFormat::RLE`].
1046 ///
1047 /// # Arguments
1048 ///
1049 /// * `data` - The bytes containing the Extended RLE data.
1050 ///
1051 /// # Returns
1052 ///
1053 /// A `Result` containing either the created `Pattern` or an error.
1054 ///
1055 /// # Errors
1056 ///
1057 /// Returns an error if:
1058 /// - Data is invalid
1059 /// - Algorithm has more than two states
1060 /// - Rule is not B3/S23
1061 ///
1062 /// # Performance Considerations
1063 ///
1064 /// The current implementation struggles with highly non-square patterns
1065 /// (like very long and narrow ones), as it fills a square grid based on the
1066 /// maximum dimension. This can lead to excessive memory usage for patterns
1067 /// with disparate dimensions. Consider using [`PatternFormat::Macrocell`]
1068 /// or [`PatternFormat::CompressedMacrocell`] instead.
1069 fn from_rle(data: &[u8]) -> Result<Self> {
1070 let mut lines = data
1071 .split(|&b| b == b'\n')
1072 .map(|x| x.strip_suffix(b"\r").unwrap_or(x))
1073 .filter(|x| !x.is_empty() && x[0] != b'#');
1074 let width: usize;
1075 let height: usize;
1076
1077 // Parse header
1078 if let Some(line) = lines.next() {
1079 // Parse x, y and rule
1080 let mut parts = line.split(|&b| b == b',').map(|x| x.trim_ascii());
1081
1082 let extract_value = |part: &[u8], expected_key: &[u8]| {
1083 let mut items = part.split(|&b| b == b'=');
1084 let key = items.next().unwrap_or(&[]).trim_ascii_end();
1085 if key != expected_key {
1086 return Err(anyhow!(
1087 "Invalid header: expected {}, got {}",
1088 String::from_utf8_lossy(expected_key),
1089 String::from_utf8_lossy(key)
1090 ));
1091 }
1092 let value = items.next().unwrap_or(&[]).trim_ascii_start();
1093 if items.next().is_some() {
1094 return Err(anyhow!("Invalid header: missing ',' between '='"));
1095 }
1096 Ok(value.to_vec())
1097 };
1098
1099 let value = extract_value(
1100 parts
1101 .next()
1102 .ok_or_else(|| anyhow!("Invalid header: missing \"x\""))?,
1103 b"x",
1104 )?;
1105 width = std::str::from_utf8(&value)?.parse()?;
1106
1107 let value = extract_value(
1108 parts
1109 .next()
1110 .ok_or_else(|| anyhow!("Invalid header: missing \"y\""))?,
1111 b"y",
1112 )?;
1113 height = std::str::from_utf8(&value)?.parse()?;
1114
1115 // rule is optional
1116 if let Some(rule) = parts.next().map(|part| extract_value(part, b"rule")) {
1117 match rule {
1118 Ok(rule) => {
1119 if rule != b"B3/S23" {
1120 return Err(anyhow!("Only B3/S23 rule is supported"));
1121 }
1122 }
1123 Err(err) => return Err(err),
1124 }
1125 }
1126 } else {
1127 return Err(anyhow!("Missing header"));
1128 }
1129
1130 // Calculate size and prepare cells array
1131 let size_log2 = (width.max(height)).next_power_of_two().ilog2();
1132 let n = 1usize << size_log2;
1133 let stride = n.max(8);
1134 let mut cells = vec![0u8; (n * n).div_ceil(8).max(n)];
1135
1136 // Parse pattern data
1137 let mut x = 0;
1138 let mut y = 0;
1139 let mut count = 0;
1140
1141 for line in lines {
1142 for &b in line {
1143 match b {
1144 b'0'..=b'9' => count = count * 10 + (b - b'0') as usize,
1145 b'b' => {
1146 x += if count == 0 { 1 } else { count };
1147 count = 0;
1148 }
1149 b'o' => {
1150 let c = if count == 0 { 1 } else { count };
1151 for i in 0..c {
1152 if x + i >= width || y >= height {
1153 return Err(anyhow!(
1154 "Pattern data out of bounds: x = {}, y = {}",
1155 x + i,
1156 y
1157 ));
1158 }
1159 let pos = x + i + y * stride;
1160 cells[pos / 8] |= 1 << (pos % 8);
1161 }
1162 x += c;
1163 count = 0;
1164 }
1165 b'$' => {
1166 y += if count == 0 { 1 } else { count };
1167 x = 0;
1168 count = 0;
1169 }
1170 b'!' => break,
1171 b' ' => continue,
1172 _ => return Err(anyhow!("Invalid RLE character: '{}'", b as char)),
1173 }
1174 if x > width {
1175 return Err(anyhow!("Pattern data out of bounds: x = {}, y = {}", x, y));
1176 }
1177 }
1178 }
1179
1180 Self::from_packed_cells(&cells)
1181 }
1182
1183 /// Converts the pattern to the RLE format. See [`PatternFormat::RLE`].
1184 ///
1185 /// # Returns
1186 ///
1187 /// A `Result` containing either the bytes of the RLE representation
1188 /// or an error.
1189 ///
1190 /// # Errors
1191 ///
1192 /// Returns an error if pattern is too large.
1193 ///
1194 /// # Performance Considerations
1195 ///
1196 /// The current implementation struggles with highly non-square patterns
1197 /// (like very long and narrow ones), as it fills a square grid based on the
1198 /// maximum dimension. This can lead to excessive memory usage for patterns
1199 /// with disparate dimensions. Consider using [`PatternFormat::Macrocell`]
1200 /// or [`PatternFormat::CompressedMacrocell`] instead.
1201 fn to_rle(&self) -> Result<Vec<u8>> {
1202 let cells_data = self.to_packed_cells()?;
1203 let n = 1usize << self.size_log2;
1204 let stride = n.max(8);
1205
1206 // Find the bounding box
1207 let mut min_x = n;
1208 let mut max_x = 0;
1209 let mut min_y = n;
1210 let mut max_y = 0;
1211 for y in 0..n {
1212 for x in 0..n {
1213 let pos = x + y * stride;
1214 if cells_data[pos / 8] & (1 << (pos % 8)) != 0 {
1215 min_x = min_x.min(x);
1216 max_x = max_x.max(x);
1217 min_y = min_y.min(y);
1218 max_y = max_y.max(y);
1219 }
1220 }
1221 }
1222
1223 // Empty pattern
1224 if min_x > max_x || min_y > max_y {
1225 return Ok(b"x = 0, y = 0, rule = B3/S23\n!".to_vec());
1226 }
1227
1228 let mut result = format!(
1229 "x = {}, y = {}, rule = B3/S23\n",
1230 max_x - min_x + 1,
1231 max_y - min_y + 1
1232 )
1233 .into_bytes();
1234 // Encode the pattern
1235 let mut line_length = 0;
1236
1237 for y in min_y..=max_y {
1238 let mut run_length = 0u32;
1239 let mut last_state = false;
1240
1241 for x in min_x..=max_x {
1242 let pos = x + y * stride;
1243 let current_state = (cells_data[pos / 8] & (1 << (pos % 8))) != 0;
1244
1245 if x == min_x || current_state != last_state {
1246 if run_length > 0 {
1247 let mut run = Vec::new();
1248 if run_length > 1 {
1249 run.extend_from_slice(run_length.to_string().as_bytes());
1250 }
1251 run.push(if last_state { b'o' } else { b'b' });
1252
1253 if line_length + run.len() > 70 {
1254 result.push(b'\n');
1255 line_length = 0;
1256 }
1257
1258 result.extend_from_slice(&run);
1259 line_length += run.len();
1260 }
1261
1262 run_length = 1;
1263 last_state = current_state;
1264 } else {
1265 run_length += 1;
1266 }
1267 }
1268
1269 if run_length > 0 {
1270 let mut run = Vec::new();
1271 if run_length > 1 {
1272 run.extend_from_slice(run_length.to_string().as_bytes());
1273 }
1274 run.push(if last_state { b'o' } else { b'b' });
1275 if line_length + run.len() + 1 > 70 {
1276 // +1 for the $ we'll add
1277 result.push(b'\n');
1278 line_length = 0;
1279 }
1280 result.extend_from_slice(&run);
1281 line_length += run.len();
1282 }
1283
1284 if y < max_y {
1285 result.push(b'$');
1286 line_length += 1;
1287 } else {
1288 result.push(b'!');
1289 }
1290 }
1291
1292 Ok(result)
1293 }
1294
1295 /// Finds or creates a blank node (containing all dead cells) of the
1296 /// specified size.
1297 ///
1298 /// This is an internal utility method used to efficiently represent empty
1299 /// regions in the quadtree.
1300 ///
1301 /// # Arguments
1302 ///
1303 /// * `size_log2` - Log base 2 of the node's side length.
1304 /// * `kiv` - The key-index-value map storing the pattern nodes.
1305 /// * `blank_nodes` - Cache of previously created blank nodes of different sizes.
1306 ///
1307 /// # Returns
1308 ///
1309 /// The index of the found or newly created blank node.
1310 fn find_or_create_blank_node(
1311 size_log2: SizeLog2,
1312 kiv: &mut KIVMap,
1313 blank_nodes: &mut Vec<NodeIdx>,
1314 ) -> NodeIdx {
1315 let i = (size_log2.max(3) - 3) as usize;
1316 while blank_nodes.len() <= i {
1317 let next = if let Some(&b) = blank_nodes.last() {
1318 PatternNode::Node {
1319 nw: b,
1320 ne: b,
1321 sw: b,
1322 se: b,
1323 }
1324 } else {
1325 PatternNode::Leaf(0)
1326 };
1327 blank_nodes.push(kiv.find_or_create_node(next));
1328 }
1329 blank_nodes[i]
1330 }
1331
1332 /// Finds a blank node (containing all dead cells) of the specified size.
1333 ///
1334 /// Similar to `find_or_create_blank_node`, but returns `None` if the node doesn't
1335 /// already exist in the provided `kiv`. This allows the function to take a
1336 /// const reference to `KIVMap` rather than requiring a mutable reference.
1337 ///
1338 /// # Arguments
1339 ///
1340 /// * `size_log2` - Log base 2 of the node's side length.
1341 /// * `kiv` - The key-index-value map storing the pattern nodes (immutable).
1342 /// * `blank_nodes` - Cache of previously created blank nodes of different sizes.
1343 ///
1344 /// # Returns
1345 ///
1346 /// An `Option` containing the index of the found blank node, or `None` if
1347 /// the requested blank node doesn't exist in the `kiv`.
1348 fn find_blank_node(
1349 size_log2: SizeLog2,
1350 kiv: &KIVMap,
1351 blank_nodes: &mut Vec<NodeIdx>,
1352 ) -> Option<NodeIdx> {
1353 let i = (size_log2.max(3) - 3) as usize;
1354 if let Some(&b) = blank_nodes.get(i) {
1355 return Some(b);
1356 }
1357 let i = (size_log2.max(3) - 3) as usize;
1358 while blank_nodes.len() <= i {
1359 let next = if let Some(&b) = blank_nodes.last() {
1360 PatternNode::Node {
1361 nw: b,
1362 ne: b,
1363 sw: b,
1364 se: b,
1365 }
1366 } else {
1367 PatternNode::Leaf(0)
1368 };
1369 if let Some(k) = kiv.find_node(next) {
1370 blank_nodes.push(k);
1371 } else {
1372 return None;
1373 }
1374 }
1375 Some(blank_nodes[i])
1376 }
1377}
1378
1379impl Default for Pattern {
1380 /// Creates a new empty pattern with the following properties:
1381 /// - An 8x8 empty grid (size_log2 = 3)
1382 fn default() -> Self {
1383 let mut kiv = KIVMap::new();
1384 Self {
1385 root: kiv.find_or_create_node(PatternNode::Leaf(0)),
1386 size_log2: 3,
1387 kiv: KIVMap::new(),
1388 blank_nodes: vec![],
1389 }
1390 }
1391}
1392
1393/// A node is either a leaf (8x8 cells) or a non-leaf (4x4 nodes).
1394/// Cells in the leaf are stored as a 64-bit integer in row-major order.
1395#[derive(Clone, Copy, PartialEq, Eq)]
1396pub enum PatternNode {
1397 Leaf(u64),
1398 Node {
1399 nw: NodeIdx,
1400 ne: NodeIdx,
1401 sw: NodeIdx,
1402 se: NodeIdx,
1403 },
1404}
1405
1406impl PatternNode {
1407 /// Fast yet effective hash function.
1408 /// It is used for indexing nodes in the KIVMap.
1409 fn hash(&self) -> u32 {
1410 let h = match self {
1411 PatternNode::Leaf(cells) => (cells ^ (cells >> 32)) as u32,
1412 PatternNode::Node { nw, ne, sw, se } => 0u32
1413 .wrapping_add((nw).wrapping_mul(5))
1414 .wrapping_add((ne).wrapping_mul(17))
1415 .wrapping_add((sw).wrapping_mul(257))
1416 .wrapping_add((se).wrapping_mul(65537)),
1417 };
1418 h.wrapping_add(h.rotate_right(11))
1419 }
1420}
1421
1422/// Key-Index-Value map: growable hashtable with linked list chains.
1423///
1424/// This data structure efficiently stores pattern nodes with two key access methods:
1425/// 1. By index: retrieve a node by its unique integer ID
1426/// 2. By content: find a node with specific content orcreate a new one if
1427/// it doesn't exist
1428///
1429/// KIVMap is an essential component of the Pattern's quadtree implementation,
1430/// providing node deduplication to reduce memory usage in large patterns.
1431///
1432/// # Implementation Details
1433///
1434/// - Uses open addressing with chained linked lists for collision resolution
1435/// - Stores nodes contiguously in a vector for cache-friendly access
1436/// - Maintains separate array of hash table chains for fast lookups
1437/// - Automatically grows the hash table when load factor exceeds 1.0
1438/// - Uses a non-cryptographic hash function optimized for pattern nodes
1439///
1440/// # Panics
1441///
1442/// Panics on running out of indexes (when the number of nodes exceeds
1443/// the maximum value of NodeIdx).
1444#[derive(Clone)]
1445struct KIVMap {
1446 hashmap_chains: Vec<NodeIdx>,
1447 storage: Vec<NodeAndNext>,
1448 capacity_log2: u32,
1449}
1450
1451#[derive(Clone, Copy)]
1452struct NodeAndNext {
1453 node: PatternNode,
1454 next: NodeIdx,
1455}
1456
1457impl KIVMap {
1458 const INITIAL_CAPACITY_LOG2: u32 = 4;
1459
1460 fn new() -> Self {
1461 let mut storage = Vec::with_capacity(1 << Self::INITIAL_CAPACITY_LOG2);
1462 // index 0 is reserved
1463 storage.push(NodeAndNext {
1464 node: PatternNode::Leaf(0),
1465 next: 0,
1466 });
1467 KIVMap {
1468 hashmap_chains: vec![0; 1 << Self::INITIAL_CAPACITY_LOG2],
1469 storage,
1470 capacity_log2: Self::INITIAL_CAPACITY_LOG2,
1471 }
1472 }
1473
1474 fn get_node(&self, idx: NodeIdx) -> &PatternNode {
1475 &self.storage[idx as usize].node
1476 }
1477
1478 fn find_node(&self, node: PatternNode) -> Option<NodeIdx> {
1479 let i = node.hash() as usize & (self.hashmap_chains.len() - 1);
1480 let mut curr = self.hashmap_chains[i];
1481 // search for the node in the linked list
1482 while curr != 0 {
1483 if self.storage[curr as usize].node == node {
1484 return Some(curr);
1485 }
1486 curr = self.storage[curr as usize].next;
1487 }
1488 None
1489 }
1490
1491 fn find_or_create_node(&mut self, node: PatternNode) -> NodeIdx {
1492 let i = node.hash() as usize & (self.hashmap_chains.len() - 1);
1493 let mut curr = self.hashmap_chains[i];
1494 // search for the node in the linked list
1495 while curr != 0 {
1496 if self.storage[curr as usize].node == node {
1497 return curr;
1498 }
1499 curr = self.storage[curr as usize].next;
1500 }
1501
1502 self.storage.push(NodeAndNext {
1503 node,
1504 next: self.hashmap_chains[i],
1505 });
1506 let idx = (self.storage.len() - 1).try_into().expect("Index overflow");
1507 self.hashmap_chains[i] = idx;
1508 if self.storage.len() > self.hashmap_chains.len() && self.capacity_log2 != u32::BITS {
1509 self.rehash();
1510 }
1511 idx
1512 }
1513
1514 fn rehash(&mut self) {
1515 self.capacity_log2 += 1;
1516 let new_size = self.hashmap_chains.len() * 2;
1517 let mut new_chains = vec![0; new_size];
1518 for &chain in &self.hashmap_chains {
1519 let mut curr = chain as usize;
1520 while curr != 0 {
1521 let hash = self.storage[curr].node.hash() as usize;
1522 let next = self.storage[curr].next as usize;
1523 let index = hash & (new_size - 1);
1524 self.storage[curr].next = new_chains[index];
1525 new_chains[index] = curr as NodeIdx;
1526 curr = next;
1527 }
1528 }
1529 self.hashmap_chains = new_chains;
1530 }
1531}
1532
1533/// Supported formats for pattern serialization and deserialization.
1534///
1535/// Notice that only two-state patterns and B3/S23 rules are supported.
1536#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1537pub enum PatternFormat {
1538 /// Packed cells format: a flat array of bytes where each bit represents one cell.
1539 /// Cells are stored in row-major order with minimal stride of 8 cells per row.
1540 PackedCells,
1541
1542 /// [Macrocell](https://golly.sourceforge.io/Help/formats.html#mc) format:
1543 /// a text-based format that efficiently represents large and sparse patterns.
1544 ///
1545 /// This format does not support blank patterns (patterns with no alive cells) and
1546 /// extends tiny patterns to 8x8 cells.
1547 ///
1548 /// Only two-state patterns and B3/S23 rules are supported.
1549 Macrocell,
1550
1551 /// Gzip-compressed macrocell format for more efficient storage.
1552 CompressedMacrocell,
1553
1554 /// [Extended RLE](https://golly.sourceforge.io/Help/formats.html#rle) format:
1555 /// a text-based format that efficiently encodes patterns using run-length encoding.
1556 ///
1557 /// This implementation supports the extended RLE format with header and comments,
1558 /// but only for two-state patterns with B3/S23 rules.
1559 RLE,
1560}
1561
1562#[cfg(test)]
1563mod tests {
1564 use super::*;
1565 const SEED: u64 = 42;
1566 // for simple patterns
1567 const HUGE_SIZE_LOG2_LIMIT: SizeLog2 = 300;
1568 // for conversions from/to packed cells
1569 const MEDIUM_SIZE_LOG2_LIMIT: SizeLog2 = 10;
1570
1571 fn repeat_leaf(leaf: u64, size_log2: SizeLog2) -> Pattern {
1572 let mut kiv = KIVMap::new();
1573 let cells = if size_log2 >= 3 {
1574 leaf
1575 } else {
1576 let mut x = leaf.to_le_bytes();
1577 for i in 0..1 << size_log2 {
1578 x[i] &= ((1u32 << (1 << size_log2)) - 1) as u8;
1579 }
1580 for i in (1 << size_log2)..8 {
1581 x[i] = 0;
1582 }
1583 u64::from_le_bytes(x)
1584 };
1585 let mut node = kiv.find_or_create_node(PatternNode::Leaf(cells));
1586 for _ in 3..size_log2 {
1587 node = kiv.find_or_create_node(PatternNode::Node {
1588 nw: node,
1589 ne: node,
1590 sw: node,
1591 se: node,
1592 });
1593 }
1594 Pattern {
1595 root: node,
1596 size_log2,
1597 kiv,
1598 blank_nodes: vec![],
1599 }
1600 }
1601
1602 #[test]
1603 fn test_population_blank() {
1604 for size_log2 in 0..HUGE_SIZE_LOG2_LIMIT {
1605 // all cells are dead
1606 let pattern = repeat_leaf(0, size_log2);
1607 assert_eq!(pattern.population(), BigInt::ZERO);
1608 }
1609 }
1610
1611 #[test]
1612 fn test_population_sparse() {
1613 for size_log2 in 0..HUGE_SIZE_LOG2_LIMIT {
1614 // one alive cell per leaf
1615 let pattern = repeat_leaf(1, size_log2);
1616 assert_eq!(
1617 pattern.population(),
1618 BigInt::from(1u64) << (size_log2.max(3) - 3) * 2
1619 );
1620 }
1621 }
1622
1623 #[test]
1624 fn test_population_full() {
1625 for size_log2 in 0..HUGE_SIZE_LOG2_LIMIT {
1626 // all cells are alive
1627 let pattern = repeat_leaf(u64::MAX, size_log2);
1628 assert_eq!(pattern.population(), BigInt::from(1u64) << (size_log2 * 2));
1629 }
1630 }
1631
1632 #[test]
1633 fn test_metafy_glider() {
1634 // Create a glider pattern (4x4)
1635 let glider = Pattern::from_rle(b"x = 3, y = 3, rule = B3/S23\nbo$2bo$3o!").unwrap();
1636
1637 // Create metacells where ON is a smaller glider and OFF is empty
1638 let off_metacell = repeat_leaf(0, 3); // All dead cells
1639 let on_metacell =
1640 Pattern::from_packed_cells(&[0b00100000, 0b00010000, 0b01110000, 0, 0, 0, 0, 0])
1641 .unwrap();
1642
1643 // Metafy with level 1
1644 let metafied = glider.metafy([&off_metacell, &on_metacell], 1).unwrap();
1645
1646 // The metafied pattern should have 5 gliders (one for each ON cell in the original glider)
1647 assert_eq!(metafied.size_log2, 5);
1648 assert_eq!(metafied.population(), BigInt::from(5 * 5)); // 5 gliders with 5 cells each
1649 }
1650
1651 #[test]
1652 fn test_metafy_simple() {
1653 // Create a simple pattern (2x2)
1654 let pattern = Pattern::from_packed_cells(&[0b11, 0b10]).unwrap();
1655
1656 // Create simple metacells (8x8)
1657 let off_metacell = repeat_leaf(1, 3); // 1 alive cell
1658 let on_metacell = repeat_leaf(u64::MAX, 3); // All alive cells
1659
1660 let mut off_population = BigInt::from(0);
1661 let mut on_population = BigInt::from(1);
1662 let mut level = 0;
1663 let size_log2 = |level: u32| 1 + level * 3;
1664 while size_log2(level) < HUGE_SIZE_LOG2_LIMIT {
1665 let metafied = pattern
1666 .metafy([&off_metacell, &on_metacell], level)
1667 .unwrap();
1668
1669 assert_eq!(metafied.size_log2, size_log2(level));
1670 assert_eq!(
1671 metafied.population(),
1672 off_population.clone() + on_population.clone() * 3
1673 );
1674 (off_population, on_population) = (
1675 off_population * 63 + on_population.clone(),
1676 on_population * 64,
1677 );
1678 level += 1;
1679 }
1680 }
1681
1682 #[test]
1683 fn test_metafy_random() {
1684 for pattern_size_log2 in 0..6 {
1685 let pattern_total_count = BigInt::from(1) << pattern_size_log2 * 2;
1686 let pattern = Pattern::random(pattern_size_log2, Some(SEED)).unwrap();
1687 for metacell_size_log2 in 3..6 {
1688 let metacell_total_count = BigInt::from(1) << metacell_size_log2 * 2;
1689 let off_metacell = Pattern::random(metacell_size_log2, Some(SEED + 1)).unwrap();
1690 let on_metacell = Pattern::random(metacell_size_log2, Some(SEED + 2)).unwrap();
1691 let metacells = [&off_metacell, &on_metacell];
1692
1693 let mut populations = [BigInt::from(0), BigInt::from(1)];
1694 let mut total_count = BigInt::from(1);
1695 for level in 0..10 {
1696 let metafied = pattern.metafy(metacells, level).unwrap();
1697
1698 assert_eq!(
1699 metafied.size_log2,
1700 pattern_size_log2 + metacell_size_log2 * level
1701 );
1702 assert_eq!(
1703 metafied.population(),
1704 pattern.population() * &populations[1]
1705 + (&pattern_total_count - pattern.population()) * &populations[0]
1706 );
1707 populations = [
1708 &populations[0] * metacells[1].population()
1709 + (&total_count - &populations[0]) * &metacells[0].population(),
1710 &populations[1] * metacells[1].population()
1711 + (&total_count - &populations[1]) * &metacells[0].population(),
1712 ];
1713 total_count *= &metacell_total_count;
1714 }
1715 }
1716 }
1717 }
1718
1719 #[test]
1720 fn test_expand() {
1721 let mut pattern = repeat_leaf(1, 0);
1722 for size_log2 in 0..HUGE_SIZE_LOG2_LIMIT {
1723 pattern.expand(size_log2);
1724 assert_eq!(pattern.size_log2, size_log2);
1725 assert_eq!(pattern.population(), BigInt::from(1));
1726 }
1727 }
1728
1729 #[test]
1730 fn test_packed_cells_roundtrip_blank() {
1731 for size_log2 in 0..MEDIUM_SIZE_LOG2_LIMIT {
1732 let pattern = repeat_leaf(0, size_log2);
1733 let data = pattern.to_packed_cells().unwrap();
1734 let deserialized = Pattern::from_packed_cells(&data).unwrap();
1735
1736 assert_eq!(pattern.size_log2, deserialized.size_log2);
1737 assert_eq!(pattern.population(), deserialized.population());
1738 assert_eq!(pattern.hash(), deserialized.hash());
1739 }
1740 }
1741
1742 #[test]
1743 fn test_packed_cells_roundtrip_sparse() {
1744 for size_log2 in 0..MEDIUM_SIZE_LOG2_LIMIT {
1745 let pattern = repeat_leaf(1, size_log2);
1746 let data = pattern.to_packed_cells().unwrap();
1747 let deserialized = Pattern::from_packed_cells(&data).unwrap();
1748
1749 assert_eq!(pattern.size_log2, deserialized.size_log2);
1750 assert_eq!(pattern.population(), deserialized.population());
1751 assert_eq!(pattern.hash(), deserialized.hash());
1752 }
1753 }
1754
1755 #[test]
1756 fn test_packed_cells_roundtrip_full() {
1757 for size_log2 in 0..MEDIUM_SIZE_LOG2_LIMIT {
1758 let pattern = repeat_leaf(u64::MAX, size_log2);
1759 let data = pattern.to_packed_cells().unwrap();
1760 let deserialized = Pattern::from_packed_cells(&data).unwrap();
1761
1762 assert_eq!(pattern.size_log2, deserialized.size_log2);
1763 assert_eq!(pattern.population(), deserialized.population());
1764 assert_eq!(pattern.hash(), deserialized.hash());
1765 }
1766 }
1767
1768 #[test]
1769 fn test_packed_cells_roundtrip_random() {
1770 for size_log2 in 0..MEDIUM_SIZE_LOG2_LIMIT {
1771 let pattern = Pattern::random(size_log2, Some(SEED)).unwrap();
1772 let data = pattern.to_packed_cells().unwrap();
1773 let deserialized = Pattern::from_packed_cells(&data).unwrap();
1774
1775 assert_eq!(pattern.size_log2, deserialized.size_log2);
1776 assert_eq!(pattern.population(), deserialized.population());
1777 assert_eq!(pattern.hash(), deserialized.hash());
1778 }
1779 }
1780
1781 #[test]
1782 fn test_macrocell_roundtrip_sparse() {
1783 for size_log2 in 0..HUGE_SIZE_LOG2_LIMIT {
1784 let pattern = repeat_leaf(1, size_log2);
1785 let data = pattern.to_macrocell().unwrap();
1786 let deserialized = Pattern::from_macrocell(&data).unwrap();
1787
1788 assert_eq!(pattern.size_log2.max(3), deserialized.size_log2);
1789 assert_eq!(pattern.population(), deserialized.population());
1790 assert_eq!(pattern.hash(), deserialized.hash());
1791 }
1792 }
1793
1794 #[test]
1795 fn test_macrocell_roundtrip_full() {
1796 for size_log2 in 0..HUGE_SIZE_LOG2_LIMIT {
1797 let pattern = repeat_leaf(u64::MAX, size_log2);
1798 let data = pattern.to_macrocell().unwrap();
1799 let deserialized = Pattern::from_macrocell(&data).unwrap();
1800
1801 assert_eq!(pattern.size_log2.max(3), deserialized.size_log2);
1802 assert_eq!(pattern.population(), deserialized.population());
1803 assert_eq!(pattern.hash(), deserialized.hash());
1804 }
1805 }
1806
1807 #[test]
1808 fn test_macrocell_roundtrip_random() {
1809 for size_log2 in 0..MEDIUM_SIZE_LOG2_LIMIT {
1810 let pattern = Pattern::random(size_log2, Some(SEED)).unwrap();
1811 // Be careful: blank patterns are not supported
1812 let data = pattern.to_macrocell().unwrap();
1813 let deserialized = Pattern::from_macrocell(&data).unwrap();
1814
1815 assert_eq!(pattern.size_log2.max(3), deserialized.size_log2);
1816 assert_eq!(pattern.population(), deserialized.population());
1817 assert_eq!(pattern.hash(), deserialized.hash());
1818 }
1819 }
1820
1821 #[test]
1822 fn test_compressed_macrocell_roundtrip_random() {
1823 for size_log2 in 0..MEDIUM_SIZE_LOG2_LIMIT {
1824 let pattern = Pattern::random(size_log2, Some(SEED)).unwrap();
1825 // Be careful: blank patterns are not supported
1826 let data = pattern.to_compressed_macrocell().unwrap();
1827 let deserialized = Pattern::from_compressed_macrocell(&data).unwrap();
1828
1829 assert_eq!(pattern.size_log2.max(3), deserialized.size_log2);
1830 assert_eq!(pattern.population(), deserialized.population());
1831 assert_eq!(pattern.hash(), deserialized.hash());
1832 }
1833 }
1834
1835 #[test]
1836 fn test_rle_roundtrip_random() {
1837 for size_log2 in 0..MEDIUM_SIZE_LOG2_LIMIT {
1838 let pattern = Pattern::random(size_log2, Some(SEED)).unwrap();
1839 // Be careful: empty rows/columns at the top/left of the original pattern are trimmed
1840 let data = pattern.to_rle().unwrap();
1841 let deserialized = Pattern::from_rle(&data).unwrap();
1842
1843 assert_eq!(pattern.size_log2, deserialized.size_log2);
1844 assert_eq!(pattern.population(), deserialized.population());
1845 assert_eq!(pattern.hash(), deserialized.hash());
1846 }
1847 }
1848
1849 #[test]
1850 fn test_conversions_glider() {
1851 let packed = Pattern::from_packed_cells(&[0b010, 0b100, 0b111, 0]).unwrap();
1852 let rle = Pattern::from_rle(b"x = 3, y = 3, rule = B3/S23\nbo$2bo$3o!").unwrap();
1853 let macrocell = Pattern::from_macrocell(b"[M2]\n#R B3/S23\n.*.$..*$***$").unwrap();
1854
1855 assert_eq!(packed.hash(), rle.hash());
1856 assert_eq!(packed.hash(), macrocell.hash());
1857 }
1858}