diff --git a/petri/src/lib.rs b/petri/src/lib.rs index fe9ede4..2afa764 100644 --- a/petri/src/lib.rs +++ b/petri/src/lib.rs @@ -13,6 +13,17 @@ pub struct Dish { pub rules: Vec, pub types: Vec, pub groups: Vec, + #[serde(skip)] + cache: Vec, + #[serde(skip)] + match_cache: Vec, +} + +#[derive(Debug)] +struct RuleCache { + rule: usize, + variant: usize, + matches: Vec<(isize, isize)>, } #[derive(Debug, Default, Serialize, Deserialize)] @@ -359,6 +370,8 @@ impl Dish { void: true, cells: vec![Cell(0)], }], + cache: Vec::new(), + match_cache: Vec::new(), } } @@ -366,6 +379,76 @@ impl Dish { for rule in &mut self.rules { rule.generate_variants(); } + self.rebuild_cache(); + } + + pub fn rebuild_cache(&mut self) { + self.cache.clear(); + self.match_cache.clear(); + } + + pub fn update_cache(&mut self, x: isize, y: isize, width: usize, height: usize) { + fn overlap( + (x1, y1, w1, h1): (isize, isize, usize, usize), + (x2, y2, w2, h2): (isize, isize, usize, usize), + ) -> bool { + let horizontal = x1..(x1.wrapping_add_unsigned(w1)); + let vertical = y1..(y1.wrapping_add_unsigned(h1)); + horizontal.contains(&x2) + || horizontal.contains(&x2.wrapping_add_unsigned(w2)) + || vertical.contains(&y2) + || vertical.contains(&y2.wrapping_add_unsigned(h2)) + } + let edited_rect = (x, y, width, height); + + for cache in &mut self.cache { + let rule = &self.rules[cache.rule].variants[cache.variant]; + let rule_width = rule.width; + let rule_height = rule.height; + + // discard all overlapping matches + let mut i = 0; + while i < cache.matches.len() { + let matc = cache.matches[i]; + let match_rect = (matc.0, matc.1, rule_width, rule_height); + if overlap(edited_rect, match_rect) { + cache.matches.swap_remove(i); + } else { + i += 1; + } + } + // check entire changed area and add matches + let border_x = rule_width - 1; + let border_y = rule_height - 1; + + for px in (x.wrapping_sub_unsigned(border_x))..(x.wrapping_add_unsigned(width)) { + for py in (y.wrapping_sub_unsigned(border_y))..(y.wrapping_add_unsigned(height)) { + // borrow checker bad >:( + // if self.subrule_matches(px, py, &rule) { + + // } + } + } + // TODO + } + } + + pub fn fire_once(&mut self) { + if self.match_cache.is_empty() { + return; + } + let i = random::() % self.match_cache.len(); + let i = self.match_cache[i]; + let rule_cache = &self.cache[i]; + let match_pos_index = random::() % rule_cache.matches.len(); + let (x, y) = rule_cache.matches[match_pos_index]; + + let rule = &self.rules[rule_cache.rule].variants[rule_cache.variant]; + let width = rule.width; + let height = rule.height; + + self.apply_rule(x, y, rule_cache.rule, rule_cache.variant); + self.update_cache(x, y, width, height); } pub fn fire_blindly(&mut self) { @@ -381,12 +464,8 @@ impl Dish { if enabled_rules.is_empty() { return; } - let rule = random::() % enabled_rules.len(); - let rule = enabled_rules[rule]; - self.fire_rule(rule); - } - - fn fire_rule(&mut self, rule_index: usize) { + let enabled_rule_index = random::() % enabled_rules.len(); + let rule_index = enabled_rules[enabled_rule_index]; let rule = &self.rules[rule_index]; let variant_index = random::() % rule.variants.len(); let variant = &rule.variants[variant_index].clone(); @@ -397,17 +476,22 @@ impl Dish { let y = ((random::() % (CHUNK_SIZE + border_y)) as isize) .wrapping_sub_unsigned(border_y); - if !self.subrule_matches(x, y, variant) { - return; + if self.subrule_matches(x, y, variant) { + self.apply_rule(x, y, rule_index, variant_index); } + } - let fail: u8 = random(); - if rule.failrate > fail { + fn apply_rule(&mut self, x: isize, y: isize, rule_index: usize, variant_index: usize) { + let rule = &self.rules[rule_index]; + let variant = &rule.variants[variant_index].clone(); + + if rule.failrate > random() { return; } let width = variant.width; let height = variant.height; + let mut old_state = Vec::new(); for dy in 0..height { for dx in 0..width { @@ -436,6 +520,7 @@ impl Dish { RuleCellTo::Copy(x, y) => { let index = x + y * variant.width; if index >= old_state.len() { + // TODO sanitize the rules somewhere else and remove this bounds check // the copy source is outside the rule bounds continue; } @@ -494,6 +579,7 @@ impl Dish { if x >= CHUNK_SIZE || y >= CHUNK_SIZE { return; } + self.update_cache(x as isize, y as isize, 1, 1); self.chunk.set_cell(x, y, cell) } }