Compare commits

..

No commits in common. "050eee0e3cfa3d4a5b8a3de07227f1bf778029d7" and "f556c80f29f66ec949e1b40f7e5260d014ea29c2" have entirely different histories.

5 changed files with 112 additions and 114 deletions

View file

@ -1 +1 @@
{"rules":[{"name":"new rule","base":{"width":2,"height":1,"contents":[[{"One":1},"None"],[{"One":0},{"One":2}]]},"enabled":true,"flip_x":false,"flip_y":false,"rotate":true,"failrate":0},{"name":"sideways","base":{"width":2,"height":1,"contents":[[{"Group":0},{"One":0}],[{"One":0},{"Copy":[0,0]}]]},"enabled":true,"flip_x":true,"flip_y":false,"rotate":false,"failrate":164},{"name":"up","base":{"width":1,"height":2,"contents":[[{"One":0},{"Copy":[0,1]}],[{"Group":0},"None"]]},"enabled":true,"flip_x":false,"flip_y":false,"rotate":false,"failrate":148},{"name":"new rule","base":{"width":1,"height":1,"contents":[[{"One":2},{"One":3}]]},"enabled":true,"flip_x":false,"flip_y":false,"rotate":false,"failrate":0},{"name":"new rule","base":{"width":1,"height":1,"contents":[[{"One":3},{"One":4}]]},"enabled":true,"flip_x":false,"flip_y":false,"rotate":false,"failrate":0},{"name":"new rule","base":{"width":1,"height":1,"contents":[[{"One":4},{"One":5}]]},"enabled":true,"flip_x":false,"flip_y":false,"rotate":false,"failrate":0},{"name":"new rule","base":{"width":1,"height":1,"contents":[[{"One":5},{"One":0}]]},"enabled":true,"flip_x":false,"flip_y":false,"rotate":false,"failrate":0},{"name":"new rule","base":{"width":1,"height":1,"contents":[[{"One":5},{"One":0}]]},"enabled":true,"flip_x":false,"flip_y":false,"rotate":false,"failrate":0}],"types":[{"name":"air","color":[0,0,0]},{"name":"source","color":[255,255,255]},{"name":"cell #2","color":[255,216,44]},{"name":"cell #3","color":[254,131,0]},{"name":"cell #4","color":[255,73,3]},{"name":"cell #5","color":[255,8,0]}],"groups":[{"name":"fire","void":false,"cells":[5,4,3,2]}]}
{"rules":[{"name":"new rule","base":{"width":2,"height":1,"contents":[[{"One":1},"None"],[{"One":0},{"One":2}]]},"enabled":true,"flip_x":false,"flip_y":false,"rotate":true,"failrate":0},{"name":"sideways","base":{"width":2,"height":1,"contents":[[{"Group":0},{"One":0}],[{"One":0},{"Copy":[0,0]}]]},"enabled":true,"flip_x":true,"flip_y":false,"rotate":false,"failrate":164},{"name":"up","base":{"width":1,"height":2,"contents":[[{"One":0},{"Copy":[0,1]}],[{"Group":0},"None"]]},"enabled":true,"flip_x":false,"flip_y":false,"rotate":false,"failrate":0},{"name":"new rule","base":{"width":1,"height":1,"contents":[[{"One":2},{"One":3}]]},"enabled":true,"flip_x":false,"flip_y":false,"rotate":false,"failrate":0},{"name":"new rule","base":{"width":1,"height":1,"contents":[[{"One":3},{"One":4}]]},"enabled":true,"flip_x":false,"flip_y":false,"rotate":false,"failrate":0},{"name":"new rule","base":{"width":1,"height":1,"contents":[[{"One":4},{"One":5}]]},"enabled":true,"flip_x":false,"flip_y":false,"rotate":false,"failrate":0},{"name":"new rule","base":{"width":1,"height":1,"contents":[[{"One":5},{"One":0}]]},"enabled":true,"flip_x":false,"flip_y":false,"rotate":false,"failrate":0},{"name":"new rule","base":{"width":1,"height":1,"contents":[[{"One":5},{"One":0}]]},"enabled":true,"flip_x":false,"flip_y":false,"rotate":false,"failrate":0}],"types":[{"name":"air","color":[0,0,0]},{"name":"source","color":[255,255,255]},{"name":"cell #2","color":[255,216,44]},{"name":"cell #3","color":[254,131,0]},{"name":"cell #4","color":[255,73,3]},{"name":"cell #5","color":[255,8,0]}],"groups":[{"name":"fire","void":false,"cells":[5,4,3,2]}]}

View file

@ -1 +0,0 @@
{"rules":[{"name":"make maze","base":{"width":1,"height":3,"contents":[[{"One":0},{"One":1}],[{"One":0},{"One":2}],[{"One":1},{"One":1}]]},"enabled":true,"flip_x":false,"flip_y":false,"rotate":true,"failrate":0},{"name":"remove corners","base":{"width":5,"height":5,"contents":[["Any","None"],["Any","None"],[{"Group":0},"None"],["Any","None"],["Any","None"],["Any","None"],["Any","None"],["Any","None"],["Any","None"],["Any","None"],[{"Group":0},"None"],["Any","None"],[{"One":1},{"One":2}],["Any","None"],[{"Group":0},"None"],["Any","None"],["Any","None"],["Any","None"],["Any","None"],["Any","None"],["Any","None"],["Any","None"],[{"Group":0},"None"],["Any","None"],["Any","None"]]},"enabled":true,"flip_x":false,"flip_y":false,"rotate":false,"failrate":0},{"name":"flood fill","base":{"width":1,"height":2,"contents":[[{"One":2},{"Copy":[0,1]}],[{"One":3},"None"]]},"enabled":false,"flip_x":false,"flip_y":false,"rotate":true,"failrate":0},{"name":"snake_move","base":{"width":1,"height":3,"contents":[[{"One":2},{"One":3}],[{"One":2},{"One":4}],[{"One":3},{"One":4}]]},"enabled":true,"flip_x":false,"flip_y":false,"rotate":true,"failrate":0},{"name":"snake_end","base":{"width":3,"height":4,"contents":[["Any","None"],[{"One":4},{"One":5}],["Any","None"],["Any","None"],[{"One":4},{"One":2}],["Any","None"],[{"Group":2},"None"],[{"One":3},{"One":2}],[{"Group":2},"None"],["Any","None"],[{"Group":2},"None"],["Any","None"]]},"enabled":true,"flip_x":false,"flip_y":false,"rotate":true,"failrate":0},{"name":"snake_backtrack","base":{"width":1,"height":3,"contents":[[{"One":4},{"One":5}],[{"One":4},{"One":2}],[{"One":5},{"One":2}]]},"enabled":true,"flip_x":false,"flip_y":false,"rotate":true,"failrate":0},{"name":"snake_reset","base":{"width":3,"height":3,"contents":[["Any","None"],[{"Group":1},"None"],["Any","None"],[{"Group":1},"None"],[{"One":5},{"One":3}],[{"Group":1},"None"],["Any","None"],[{"Group":1},"None"],["Any","None"]]},"enabled":true,"flip_x":false,"flip_y":false,"rotate":false,"failrate":0}],"types":[{"name":"air","color":[0,0,0]},{"name":"maze node","color":[255,147,219]},{"name":"passage","color":[238,202,118]},{"name":"head","color":[0,180,88]},{"name":"tail","color":[183,254,149]},{"name":"rev_head","color":[247,101,240]},{"name":"cell #6","color":[193,0,0]}],"groups":[{"name":"traversed","void":true,"cells":[2,1]},{"name":"not_snake","void":true,"cells":[2,0]},{"name":"air","void":true,"cells":[0]}]}

View file

@ -1 +0,0 @@
{"rules":[{"name":"move","base":{"width":1,"height":2,"contents":[[{"Group":0},{"One":0}],[{"One":0},{"Copy":[0,0]}]]},"enabled":true,"flip_x":false,"flip_y":false,"rotate":true,"failrate":0},{"name":"spark","base":{"width":1,"height":2,"contents":[[{"One":1},"None"],[{"One":0},{"One":2}]]},"enabled":true,"flip_x":false,"flip_y":false,"rotate":true,"failrate":136},{"name":"fade","base":{"width":1,"height":1,"contents":[[{"One":2},{"One":0}]]},"enabled":true,"flip_x":false,"flip_y":false,"rotate":false,"failrate":174}],"types":[{"name":"air","color":[0,0,0]},{"name":"sparker","color":[255,117,212]},{"name":"spark","color":[244,179,229]}],"groups":[{"name":"empty","void":false,"cells":[2,1]}]}

View file

@ -74,12 +74,6 @@ pub struct Rule {
struct SubRule {
width: usize,
height: usize,
/// offset from top-left corner used to find and sample matches fairly, normally only used by rotated/mirrored variants
#[serde(default, skip)]
origin_x: usize,
/// offset from top-left corner used to find and sample matches fairly, normally only used by rotated/mirrored variants
#[serde(default, skip)]
origin_y: usize,
contents: Vec<(RuleCellFrom, RuleCellTo)>,
}
@ -112,8 +106,6 @@ impl SubRule {
Self {
width: 1,
height: 1,
origin_x: 0,
origin_y: 0,
contents: vec![Default::default()],
}
}
@ -244,10 +236,6 @@ impl Rule {
self.variants.len()
}
pub fn dbg_variants(&self) {
dbg!(&self.variants);
}
pub fn generate_variants(&mut self) {
self.variants.clear();
self.variants.push(self.base.clone());
@ -266,7 +254,6 @@ impl Rule {
if self.flip_x {
transform_variants(&mut self.variants, |base| {
let mut new = base.clone();
new.origin_x = new.width - new.origin_x - 1;
for y in 0..new.height {
for x in 0..new.width {
let mut cell = base.get(new.width - x - 1, y);
@ -282,7 +269,6 @@ impl Rule {
if self.flip_y {
transform_variants(&mut self.variants, |base| {
let mut new = base.clone();
new.origin_y = new.height - new.origin_y - 1;
for y in 0..new.height {
for x in 0..new.width {
let mut cell = base.get(x, new.height - y - 1);
@ -299,8 +285,6 @@ impl Rule {
// 180° rotations (same as flipping x and y)
transform_variants(&mut self.variants, |base| {
let mut new = base.clone();
new.origin_x = new.width - new.origin_x - 1;
new.origin_y = new.height - new.origin_y - 1;
for y in 0..new.height {
for x in 0..new.width {
let mut cell = base.get(new.width - x - 1, new.height - y - 1);
@ -319,8 +303,6 @@ impl Rule {
let mut new = base.clone();
new.height = base.width;
new.width = base.height;
new.origin_x = base.height - base.origin_y - 1;
new.origin_y = base.origin_x;
for y in 0..new.height {
for x in 0..new.width {
let mut cell = base.get(y, new.width - x - 1);
@ -381,8 +363,6 @@ impl Dish {
base: SubRule {
width: 1,
height: 2,
origin_x: 0,
origin_y: 0,
contents: vec![
(RuleCellFrom::One(Cell(1)), RuleCellTo::One(Cell(0))),
(RuleCellFrom::One(Cell(0)), RuleCellTo::One(Cell(1))),
@ -396,8 +376,6 @@ impl Dish {
base: SubRule {
width: 2,
height: 2,
origin_x: 0,
origin_y: 0,
contents: vec![
(RuleCellFrom::One(Cell(1)), RuleCellTo::One(Cell(0))),
(RuleCellFrom::Any, RuleCellTo::None),
@ -437,14 +415,6 @@ impl Dish {
new
}
pub fn cache_count(&self) -> usize {
self.cache.iter().map(|c| c.matches.len()).sum()
}
pub fn dbg_cache(&self) {
dbg!(&self.cache);
}
pub fn fill(&mut self, cell: Cell) {
self.world.fill(cell);
self.rebuild_cache();
@ -490,14 +460,9 @@ impl Dish {
let border_x = rule.width as isize - 1;
let border_y = rule.height as isize - 1;
for px in -border_x..(CHUNK_SIZE as isize + border_x) {
for py in -border_y..(CHUNK_SIZE as isize + border_y) {
let corner_x = px.wrapping_sub_unsigned(rule.origin_x);
let corner_y = py.wrapping_sub_unsigned(rule.origin_y);
if self
.world
.subrule_matches(corner_x, corner_y, rule, &self.groups)
{
for px in -border_x..(CHUNK_SIZE as isize) {
for py in -border_y..(CHUNK_SIZE as isize) {
if self.world.subrule_matches(px, py, rule, &self.groups) {
matches.push((px, py));
}
}
@ -519,7 +484,7 @@ impl Dish {
self.update_match_cache();
}
pub fn update_cache(&mut self, cx: isize, cy: isize, width: usize, height: usize) {
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),
@ -529,7 +494,7 @@ impl Dish {
&& y2 < y1.saturating_add_unsigned(h1)
&& y1 < y2.saturating_add_unsigned(h2)
}
let edited_rect = (cx, cy, width, height);
let edited_rect = (x, y, width, height);
for cache in &mut self.cache {
let rule = &self.rules[cache.rule].variants[cache.variant];
@ -540,9 +505,7 @@ impl Dish {
let mut i = 0;
while i < cache.matches.len() {
let match_pos = cache.matches[i];
let m_corner_x = match_pos.0.wrapping_sub_unsigned(rule.origin_x);
let m_corner_y = match_pos.1.wrapping_sub_unsigned(rule.origin_y);
let match_rect = (m_corner_x, m_corner_y, rule_width, rule_height);
let match_rect = (match_pos.0, match_pos.1, rule_width, rule_height);
if overlap(edited_rect, match_rect) {
cache.matches.swap_remove(i);
} else {
@ -553,20 +516,9 @@ impl Dish {
let border_x = rule_width - 1;
let border_y = rule_height - 1;
let x_min = cx.wrapping_sub_unsigned(border_x);
let y_min = cy.wrapping_sub_unsigned(border_y);
let x_max = cx
.wrapping_add_unsigned(width)
.wrapping_add_unsigned(border_x);
let y_max = cy
.wrapping_add_unsigned(height)
.wrapping_add_unsigned(border_y);
for px in x_min..x_max {
for py in y_min..y_max {
let cx = px.wrapping_sub_unsigned(rule.origin_x);
let cy = py.wrapping_sub_unsigned(rule.origin_y);
if self.world.subrule_matches(cx, cy, rule, &self.groups) {
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)) {
if self.world.subrule_matches(px, py, rule, &self.groups) {
cache.matches.push((px, py));
}
}
@ -598,40 +550,52 @@ impl Dish {
let rule = &self.rules[rule_cache.rule].variants[rule_cache.variant];
let width = rule.width;
let height = rule.height;
let cx = x.wrapping_sub_unsigned(rule.origin_x);
let cy = y.wrapping_sub_unsigned(rule.origin_y);
self.apply_rule(x, y, rule_cache.rule, rule_cache.variant);
self.update_cache(cx, cy, width, height);
self.update_cache(x, y, width, height);
}
/// Picks a random point and applies a random match at that position, if any exist.
/// The random point can be outside the world bounds, to catch cases where the origin of a match is outside the bounds.
/// The random point can be outside the world bounds, to catch cases where the root (top-left) of a match is outside the bounds.
/// TODO make sure max_rule_[width/height] is up to date after each rule.generate_variants
pub fn try_one_location(&mut self) {
pub fn fire_blindly_cached(&mut self) {
let border_x = self.max_rule_width - 1;
let border_y = self.max_rule_height - 1;
let origin_x = ((random::<usize>() % (CHUNK_SIZE + border_x * 2)) as isize)
let x = ((random::<usize>() % (CHUNK_SIZE + border_x)) as isize)
.wrapping_sub_unsigned(border_x);
let origin_y = ((random::<usize>() % (CHUNK_SIZE + border_y * 2)) as isize)
let y = ((random::<usize>() % (CHUNK_SIZE + border_y)) as isize)
.wrapping_sub_unsigned(border_y);
let matches = self.get_matches_at_point(origin_x, origin_y);
let matches = self.get_matches_at_point(x, y);
if matches.is_empty() {
return;
}
let i = random::<usize>() % matches.len();
let (rule_index, variant_index) = matches[i];
self.apply_rule(origin_x, origin_y, rule_index, variant_index);
let variant = &self.rules[rule_index].variants[variant_index];
let width = variant.width;
let height = variant.height;
self.update_cache(
origin_x.wrapping_sub_unsigned(variant.origin_x),
origin_y.wrapping_sub_unsigned(variant.origin_y),
width,
height,
);
self.apply_rule(x, y, rule_index, variant_index);
let rule = &self.rules[rule_index].variants[variant_index];
let width = rule.width;
let height = rule.height;
self.update_cache(x, y, width, height);
}
/// Picks a random point and applies a random match that overlaps with it, if any exist.
/// TODO benchmark and only keep one of try_one_position_overlapped and fire_blindly_cached
pub fn try_one_position_overlapped(&mut self) {
let x = (random::<usize>() % CHUNK_SIZE) as isize;
let y = (random::<usize>() % CHUNK_SIZE) as isize;
let matches = self.get_matches_containing_point(x, y);
if matches.is_empty() {
return;
}
let i = random::<usize>() % matches.len();
let (x, y, rule_index, variant_index) = matches[i];
self.apply_rule(x, y, rule_index, variant_index);
let rule = &self.rules[rule_index].variants[variant_index];
let width = rule.width;
let height = rule.height;
self.update_cache(x, y, width, height);
}
fn get_matches_at_point(&self, x: isize, y: isize) -> Vec<(usize, usize)> {
@ -645,6 +609,62 @@ impl Dish {
.collect()
}
fn get_matches_containing_point(
&self,
x: isize,
y: isize,
) -> Vec<(isize, isize, usize, usize)> {
fn contains((x, y, w, h): (isize, isize, usize, usize), (px, py): (isize, isize)) -> bool {
px >= x
&& py >= y && px < x.saturating_add_unsigned(w)
&& py < y.saturating_add_unsigned(h)
}
self.cache
.iter()
.flat_map(|rule| {
let variant = &self.rules[rule.rule].variants[rule.variant];
let (w, h) = (variant.width, variant.height);
rule.matches.iter().filter_map(move |&(mx, my)| {
if contains((mx, my, w, h), (x, y)) {
Some((mx, my, rule.rule, rule.variant))
} else {
None
}
})
})
.collect()
}
pub fn fire_blindly(&mut self) {
if self.rules.is_empty() {
return;
}
let enabled_rules = self
.rules
.iter()
.enumerate()
.filter_map(|(i, r)| r.enabled.then_some(i))
.collect::<Vec<_>>();
if enabled_rules.is_empty() {
return;
}
let enabled_rule_index = random::<usize>() % enabled_rules.len();
let rule_index = enabled_rules[enabled_rule_index];
let rule = &self.rules[rule_index];
let variant_index = random::<usize>() % rule.variants.len();
let variant = &rule.variants[variant_index].clone();
let border_x = variant.width - 1;
let border_y = variant.height - 1;
let x = ((random::<usize>() % (CHUNK_SIZE + border_x)) as isize)
.wrapping_sub_unsigned(border_x);
let y = ((random::<usize>() % (CHUNK_SIZE + border_y)) as isize)
.wrapping_sub_unsigned(border_y);
if self.world.subrule_matches(x, y, variant, &self.groups) {
self.apply_rule(x, y, rule_index, variant_index);
}
}
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();
@ -660,25 +680,16 @@ impl Dish {
let mut old_state = Vec::new();
for dy in 0..height {
for dx in 0..width {
let x = x
.wrapping_add_unsigned(dx)
.wrapping_sub_unsigned(variant.origin_x) as usize;
let y = y
.wrapping_add_unsigned(dy)
.wrapping_sub_unsigned(variant.origin_y) as usize;
old_state.push(self.get_cell(x, y));
old_state.push(
self.get_cell((x as usize).wrapping_add(dx), (y as usize).wrapping_add(dy)),
);
}
}
for dx in 0..width {
for dy in 0..height {
let px = x
.wrapping_add_unsigned(dx)
.wrapping_sub_unsigned(variant.origin_x) as usize;
let py = y
.wrapping_add_unsigned(dy)
.wrapping_sub_unsigned(variant.origin_y) as usize;
let px = x.wrapping_add_unsigned(dx) as usize;
let py = y.wrapping_add_unsigned(dy) as usize;
match variant.get(dx, dy).1 {
RuleCellTo::One(rule_cell) => {
self.set_cell(px, py, rule_cell);
@ -738,17 +749,11 @@ impl World {
}
}
fn subrule_matches(
&self,
corner_x: isize,
corner_y: isize,
subrule: &SubRule,
groups: &[CellGroup],
) -> bool {
fn subrule_matches(&self, x: isize, y: isize, subrule: &SubRule, groups: &[CellGroup]) -> bool {
for dx in 0..subrule.width {
for dy in 0..subrule.height {
let x = corner_x.wrapping_add_unsigned(dx) as usize;
let y = corner_y.wrapping_add_unsigned(dy) as usize;
let x = x.wrapping_add_unsigned(dx) as usize;
let y = y.wrapping_add_unsigned(dy) as usize;
let cell = self.get_cell(x, y);
match subrule.get(dx, dy).0 {
RuleCellFrom::One(rule_cell) => {

View file

@ -79,8 +79,12 @@ impl eframe::App for UScope {
ctx.request_repaint();
let sim_frame = Instant::now();
for _ in 0..self.speed {
self.dish.try_one_location();
// self.dish.apply_one_match();
// benchmarks made with sand_stress_test at 50000 speed in a release build
// ~50ms
self.dish.try_one_position_overlapped();
// ~35ms
// TODO: has directional bias, figure out why and fix it
// self.dish.fire_blindly_cached();
}
let sim_time = sim_frame.elapsed();
// self.sim_times.push(sim_time.as_micros());
@ -103,9 +107,6 @@ impl eframe::App for UScope {
if ui.button("regenerate rules and cache").clicked() {
self.dish.update_all_rules();
}
if ui.button("debug cache").clicked() {
self.dish.dbg_cache();
}
ui.horizontal(|ui| {
if ui.button("Save").clicked() {
self.save_universe();
@ -222,11 +223,8 @@ impl eframe::App for UScope {
self.brush = clicked_cell;
}
} else {
let old = self.dish.get_cell(x, y);
if Some(self.brush) != old {
self.dish.set_cell(x, y, self.brush);
self.dish.update_cache(x as isize, y as isize, 1, 1);
}
self.dish.set_cell(x, y, self.brush);
self.dish.update_cache(x as isize, y as isize, 1, 1);
}
}
});
@ -300,9 +298,6 @@ fn rule_editor(
ui.label("fail rate:");
ui.add(DragValue::new(&mut rule.failrate));
ui.label(format!("variants: {}", rule.variant_count()));
if ui.button("debug").clicked() {
rule.dbg_variants();
}
});
let cells_y = rule.height();
let cells_x = rule.width();