diff --git a/common/src/astar2.rs b/common/src/astar2.rs index e474362bf2..79cf9c8c02 100644 --- a/common/src/astar2.rs +++ b/common/src/astar2.rs @@ -11,9 +11,8 @@ use std::collections::BinaryHeap; #[derive(Copy, Clone, Debug)] pub struct PathEntry { // cost so far + heursitic - priority: f32, + cost_estimate: f32, node: S, - //cost: f32, } impl PartialEq for PathEntry { @@ -26,7 +25,10 @@ impl Ord for PathEntry { // This method implements reverse ordering, so that the lowest cost // will be ordered first fn cmp(&self, other: &PathEntry) -> Ordering { - other.priority.partial_cmp(&self.priority).unwrap_or(Equal) + other + .cost_estimate + .partial_cmp(&self.cost_estimate) + .unwrap_or(Equal) } } @@ -40,7 +42,7 @@ impl PartialOrd for PathEntry { // manner with `Ord`, but I don't think we need to care about that here(?) // // See note about reverse ordering above. - fn le(&self, other: &PathEntry) -> bool { other.priority <= self.priority } + fn le(&self, other: &PathEntry) -> bool { other.cost_estimate <= self.cost_estimate } } pub enum PathResult { @@ -78,32 +80,29 @@ struct NodeEntry { #[derive(Clone, Debug)] struct Cluster { + // idea: if we store pointers to adjacent clusters we could avoid the hashmap entirely when + // accessing neighboring nodes, actually I'm not even sure what case we would need a hashmap + // for in this scenario if we store cluster pointer in the priority queue (or index to cluster + // pointer if saving space here is worth it)! // TODO: we could use `(S, u8)` here? - // idea: if we bake in the gridness we could just store a direction + // idea: if we bake in the gridness we could just store a direction (note: actually some things + // like bridges are not adjacent) + // idea: if we can gain something by making them smaller, we could allocate clusters in a Vec, + // this amoritizes allocation costs some, if we point to neighbors we would need to use + // indices although we could take advantage of that to make them smaller than pointer size + // (alternatively a bump allocator would work very well here) + // idea came_from: [Option; 256], cheapest_score: [f32; 256], } -// ideas: -// * merge hashmaps -// * "chunked" exploration -// * things we put on priority queue don't need to point into a hashmap (i.e. we -// only need a hashmap to map from new/unknown nodes to whatever -// datastructure) #[derive(Clone)] pub struct Astar { iter: usize, max_iters: usize, potential_nodes: BinaryHeap>, // cost, node pairs - // converting to single hash structure: 11349 ms -> 10462 ms / 10612 ms - // with two hash structures (came_from and cheapest_scores): 10861 ms visited_nodes: HashMap, Hasher>, - // -> 25055 ms -> 15771 ms with Box -> fixed bugs 10731 ms, hmmm - clusters: HashMap>, Hasher>, // TODO: Box cluster? - //came_from: HashMap, - //cheapest_scores: HashMap, - //final_scores: HashMap, - //visited: HashSet, + clusters: HashMap>, Hasher>, start_node: S, cheapest_node: Option, cheapest_cost: Option, @@ -120,29 +119,10 @@ impl Astar { max_iters, iter: 0, potential_nodes: core::iter::once(PathEntry { - priority: 0.0, - //cost: 0.0, + cost_estimate: 0.0, node: start.clone(), }) .collect(), - /* - came_from: HashMap::with_hasher(hasher.clone()), - cheapest_scores: { - let mut h = HashMap::with_capacity_and_hasher(1, hasher.clone()); - h.extend(core::iter::once((start.clone(), 0.0))); - h - }, - final_scores: { - let mut h = HashMap::with_capacity_and_hasher(1, hasher.clone()); - h.extend(core::iter::once((start.clone(), 0.0))); - h - }, - visited: { - let mut s = HashSet::with_capacity_and_hasher(1, hasher); - s.extend(core::iter::once(start)); - s - }, - */ visited_nodes: { let mut s = HashMap::with_capacity_and_hasher(1, hasher.clone()); s.extend(core::iter::once((start.clone(), NodeEntry { @@ -161,26 +141,21 @@ impl Astar { pub fn poll( &mut self, iters: usize, - // estimate how far we are from the target? but we are given two nodes... (current, + // Estimate how far we are from the target? but we are given two nodes... (current, // previous) mut heuristic: impl FnMut(&S, &S) -> f32, // get neighboring nodes mut neighbors: impl FnMut(&S) -> I, - // cost of edge between these two nodes - // I assume this is (source, destination)? - mut transition: impl FnMut(&S, &S) -> f32, - // have we reached a/the target? + // have we reached target? mut satisfied: impl FnMut(&S) -> bool, // this function clusters nodes together for cache locality purposes // output (cluster base, offset in cluster) cluster: impl Fn(&S) -> (S, u8), ) -> PathResult where - // Combining transition into this: 9913 ms -> 8204 ms (~1.7 out of ~6.5 seconds) - I: Iterator, + I: Iterator, // (node, transition cost) { /* - */ if self.clusters.is_empty() { let (key, index) = cluster(&self.start_node); let mut came_from = std::array::from_fn(|_| None); @@ -193,14 +168,14 @@ impl Astar { }), ); } + */ let iter_limit = self.max_iters.min(self.iter + iters); while self.iter < iter_limit { - // pop highest priority node if let Some(PathEntry { node, .. }) = self.potential_nodes.pop() { - // if this is the destination, we return if satisfied(&node) { return PathResult::Path(self.reconstruct_path_to(node, cluster)); } else { + /* let (cluster_key, index) = cluster(&node); let (node_cheapest, came_from) = self .clusters @@ -212,30 +187,18 @@ impl Astar { ) }) .unwrap(); - // regression - //if node_cheapest < cost { - // we already processed it - // continue; - //} - // 10700 ms -> 10477 ms (moving this out of the loop) - // we have to fetch this even though it was put into the priority queu - /* - let node_cheapest = self + */ + // we have to fetch this even though it was put into the priority queue + let (node_cheapest, came_from) = self .visited_nodes .get(&node) - .map_or(f32::MAX, |n| n.cheapest_score); - */ - // otherwise we iterate neighbors - // TODO: try for_each here - // 6879 ms -> 6989 ms (regression using for_each) - //neighbors(&node).for_each(|(neighbor, transition)| { + .map(|n| (n.cheapest_score, n.came_from.clone())) + .unwrap(); for (neighbor, transition) in neighbors(&node) { - // skipping here: 10694 ms -> 9913 ms (almost whole second out of 7 taken - // for this, this is because the `transition` call is fairly expensive) if neighbor == came_from { continue; - //return; } + /* let (cluster_key, index) = cluster(&neighbor); let mut previously_visited = false; let neighbor_cheapest = self @@ -247,44 +210,18 @@ impl Astar { previously_visited.then(|| c.cheapest_score[usize::from(index)]) }) .unwrap_or(f32::MAX); - /* + */ let neighbor_cheapest = self .visited_nodes .get(&neighbor) .map_or(f32::MAX, |n| n.cheapest_score); - */ - // 10573 ms -> 11546 ms (with entry api appears to be regression) - /* - let mut previously_visited = true; - let neighbor_entry = self - .visited_nodes - .entry(neighbor.clone()) - .or_insert_with(|| { - previously_visited = false; - NodeEntry { - came_from: node.clone(), - cheapest_score: f32::MAX, - } - }); - let neighbor_cheapest = neighbor_entry.cheapest_score; - */ - /* - let node_cheapest = *self.cheapest_scores.get(&node).unwrap_or(&f32::MAX); - let neighbor_cheapest = - *self.cheapest_scores.get(&neighbor).unwrap_or(&f32::MAX); - */ - // TODO: have caller provide transition cost with neighbors iterator (so - // that duplicate costs in `transition` can be avoided?) // compute cost to traverse to each neighbor - let cost = node_cheapest + transition; //transition(&node, &neighbor); - // if this is cheaper than existing cost for that neighbor (or neighbor - // hasn't been visited) - // can we convince ourselves that this is always true if node was not - // visited? + let cost = node_cheapest + transition; + if cost < neighbor_cheapest { - //neighbor_entry.cheapest_score = cost; /* + neighbor_entry.cheapest_score = cost; // note: unconditional insert, same cost as overwriting if it already // exists let previously_visited = self @@ -293,7 +230,6 @@ impl Astar { .is_some(); self.cheapest_scores.insert(neighbor.clone(), cost); */ - /* let previously_visited = self .visited_nodes .insert(neighbor.clone(), NodeEntry { @@ -301,7 +237,7 @@ impl Astar { cheapest_score: cost, }) .is_some(); - */ + /* let cluster_mut = self.clusters.entry(cluster_key).or_insert_with(|| { Box::new(Cluster { @@ -312,37 +248,28 @@ impl Astar { cluster_mut.came_from[usize::from(index)] = Some(node.clone()); cluster_mut.cheapest_score[usize::from(index)] = cost; + */ let h = heuristic(&neighbor, &node); // note that cheapest_scores does not include the heuristic - // this is what final_scores does, priority queue does include - // heuristic - let priority = cost + h; - // note this is literally unused, removing saves ~350 ms out of 11349 - // (note this is all of startup time) - //self.final_scores.insert(neighbor.clone(), neighbor_cost); + // priority queue does include heuristic + let cost_estimate = cost + h; if self.cheapest_cost.map(|cc| h < cc).unwrap_or(true) { self.cheapest_node = Some(node.clone()); self.cheapest_cost = Some(h); }; - // commenting out if here: 11349 ms -> 12498 ms (but may give better - // paths?) (about 1 extra second or +10% time) - // with single hashmap change this has much more impact: - // 3473 ms -> 11981 ms - + // TODO: I think the if here should be removed // if we hadn't already visted, add this to potential nodes, what about // its neighbors, wouldn't they need to be revisted??? if !previously_visited { self.potential_nodes.push(PathEntry { - priority, - //cost, + cost_estimate, node: neighbor, }); } } } - //}); } } else { return PathResult::None( @@ -370,29 +297,29 @@ impl Astar { pub fn get_cheapest_cost(&self) -> Option { self.cheapest_cost } - // At least in world site pathfinding this is super cheap compared to actually - // finding the path! fn reconstruct_path_to(&mut self, end: S, cluster: impl Fn(&S) -> (S, u8)) -> Path { let mut path = vec![end.clone()]; let mut cnode = &end; - let (mut ckey, mut ci) = cluster(cnode); - while let Some(node) = self - .clusters - .get(&ckey) - .and_then(|c| c.came_from[usize::from(ci)].as_ref()) - .filter(|n| *n != cnode) /* - self + let (mut ckey, mut ci) = cluster(cnode); + while let Some(node) = + self + .clusters + .get(&ckey) + .and_then(|c| c.came_from[usize::from(ci)].as_ref()) + .filter(|n| *n != cnode) + */ + /* + */ + while let Some(node) = self .visited_nodes .get(cnode) .map(|n| &n.came_from) .filter(|n| *n != cnode) - */ - //self.came_from.get(cnode) { path.push(node.clone()); cnode = node; - (ckey, ci) = cluster(cnode); + //(ckey, ci) = cluster(cnode); } path.into_iter().rev().collect() } diff --git a/world/src/civ/mod.rs b/world/src/civ/mod.rs index 09df31d2d9..a97021b0c4 100644 --- a/world/src/civ/mod.rs +++ b/world/src/civ/mod.rs @@ -55,16 +55,10 @@ pub struct Civs { /// (3) we have 8-byte keys (for which FxHash is fastest). pub track_map: DHashMap, DHashMap, Id>>, - // 8249 ms -> 7680 ms (change when switching to ahash) - // 7495 ms -> 8057 ms -> 7481 ms (ahash -> sip13 -> fxhasher) - // TODO: deterministic(?), this is certainly faster, presumably due to less collisions pub bridges: hashbrown::HashMap< Vec2, (Vec2, Id), - //std::hash::BuildHasherDefault, std::hash::BuildHasherDefault, - //std::hash::BuildHasherDefault, - //std::hash::BuildHasherDefault, // too many collisions! >, pub sites: Store, @@ -527,8 +521,6 @@ impl Civs { } } } - - dbg!(CC.load(Ordering::Relaxed)); } // TODO: Move this @@ -1120,7 +1112,6 @@ impl Civs { loc: Vec2, site_fn: impl FnOnce(Id) -> Site, ) -> Id { - prof_span!("establish site inner"); let place = match ctx.sim.get(loc).and_then(|site| site.place) { Some(place) => place, None => civs.establish_place(ctx, loc, SITE_AREA), @@ -1132,11 +1123,15 @@ impl Civs { let site = establish_site(self, ctx, loc, site_fn); // Find neighbors - prof_span!(guard, "find neighbors"); - const MAX_NEIGHBOR_DISTANCE: f32 = 2000.0; + // Note, the maximum distance that I have so far observed not hitting the + // iteration limit in `find_path` is 297. So I think this is a reasonible + // limit (although the relationship between distance and pathfinding iterations + // can be a bit variable). + const MAX_NEIGHBOR_DISTANCE: f32 = 350.0; let mut nearby = self .sites .iter() + .filter(|&(id, _)| id != site) .filter(|(_, p)| { matches!( p.kind, @@ -1152,7 +1147,6 @@ impl Civs { .filter(|(_, dist)| *dist < MAX_NEIGHBOR_DISTANCE) .collect::>(); nearby.sort_by_key(|(_, dist)| *dist as i32); - drop(guard); if let SiteKind::Refactor | SiteKind::Settlement @@ -1161,111 +1155,107 @@ impl Civs { | SiteKind::DesertCity | SiteKind::Castle = self.sites[site].kind { - for (nearby, _) in nearby.into_iter().take(5) { + for (nearby, _) in nearby.into_iter().take(4) { prof_span!("for nearby"); - // Find a novel path - let maybe_path = { - prof_span!("find path"); - find_path( - ctx, - |start| self.bridges.get(&start).map(|(end, _)| *end), - loc, - self.sites.get(nearby).center, - ) - }; - if maybe_path.is_some() { - info!("Succeed"); - } else { - info!("Fail"); - } - if let Some((path, cost)) = maybe_path { - prof_span!("with path"); - // Find a path using existing paths - if self + // Find a route using existing paths + // + // If the novel path isn't efficient compared to this, don't use it + let max_novel_cost = { + let max_novel_cost = self .route_between(site, nearby) - // If the novel path isn't efficient compared to existing routes, don't use it - .filter(|(_, route_cost)| *route_cost < cost * 3.0) - .is_none() - { - // Write the track to the world as a path - for locs in path.nodes().windows(3) { - let mut randomize_offset = false; - if let Some((i, _)) = NEIGHBORS - .iter() - .enumerate() - .find(|(_, dir)| **dir == locs[0] - locs[1]) - { - ctx.sim.get_mut(locs[0]).unwrap().path.0.neighbors |= - 1 << ((i as u8 + 4) % 8); - ctx.sim.get_mut(locs[1]).unwrap().path.0.neighbors |= - 1 << (i as u8); - randomize_offset = true; - } - - if let Some((i, _)) = NEIGHBORS - .iter() - .enumerate() - .find(|(_, dir)| **dir == locs[2] - locs[1]) - { - ctx.sim.get_mut(locs[2]).unwrap().path.0.neighbors |= - 1 << ((i as u8 + 4) % 8); - ctx.sim.get_mut(locs[1]).unwrap().path.0.neighbors |= - 1 << (i as u8); - randomize_offset = true; - } else if !self.bridges.contains_key(&locs[1]) { - //dbg!("here"); called 18 times - let center = (locs[1] + locs[2]) / 2; - let id = - establish_site(self, &mut ctx.reseed(), center, move |place| { - Site { - kind: SiteKind::Bridge(locs[1], locs[2]), - site_tmp: None, - center, - place, - } - }); - self.bridges.insert(locs[1], (locs[2], id)); - self.bridges.insert(locs[2], (locs[1], id)); - } - /* - let to_prev_idx = NEIGHBORS - .iter() - .enumerate() - .find(|(_, dir)| **dir == (locs[0] - locs[1]).map(|e| e.signum())) - .expect("Track locations must be neighbors") - .0; - - let to_next_idx = NEIGHBORS - .iter() - .enumerate() - .find(|(_, dir)| **dir == (locs[2] - locs[1]).map(|e| e.signum())) - .expect("Track locations must be neighbors") - .0; + .map_or(f32::MAX, |(_, route_cost)| route_cost / 3.0); + max_novel_cost + }; + let start = loc; + let end = self.sites.get(nearby).center; + // Find a novel path. + // + // We rely on the cost at least being equal to the distance, to avoid + // unnecessary novel pathfinding. + let maybe_path = ((start.distance_squared(end) as f32).sqrt() < max_novel_cost) + .then(|| { + prof_span!("find path"); + let get_bridge = |start| self.bridges.get(&start).map(|(end, _)| *end); + find_path(ctx, get_bridge, start, end) + }) + .flatten() + .filter(|&(_, cost)| cost < max_novel_cost); + if let Some((path, cost)) = maybe_path { + // Write the track to the world as a path + for locs in path.nodes().windows(3) { + let mut randomize_offset = false; + if let Some((i, _)) = NEIGHBORS + .iter() + .enumerate() + .find(|(_, dir)| **dir == locs[0] - locs[1]) + { ctx.sim.get_mut(locs[0]).unwrap().path.0.neighbors |= - 1 << ((to_prev_idx as u8 + 4) % 8); - ctx.sim.get_mut(locs[2]).unwrap().path.0.neighbors |= - 1 << ((to_next_idx as u8 + 4) % 8); - let mut chunk = ctx.sim.get_mut(locs[1]).unwrap(); - chunk.path.0.neighbors |= - (1 << (to_prev_idx as u8)) | (1 << (to_next_idx as u8)); - */ - if randomize_offset { - let mut chunk = ctx.sim.get_mut(locs[1]).unwrap(); - chunk.path.0.offset = Vec2::new( - ctx.rng.gen_range(-16..17), - ctx.rng.gen_range(-16..17), - ); - } + 1 << ((i as u8 + 4) % 8); + ctx.sim.get_mut(locs[1]).unwrap().path.0.neighbors |= 1 << (i as u8); + randomize_offset = true; } - // Take note of the track - let track = self.tracks.insert(Track { cost, path }); - self.track_map - .entry(site) - .or_default() - .insert(nearby, track); + if let Some((i, _)) = NEIGHBORS + .iter() + .enumerate() + .find(|(_, dir)| **dir == locs[2] - locs[1]) + { + ctx.sim.get_mut(locs[2]).unwrap().path.0.neighbors |= + 1 << ((i as u8 + 4) % 8); + ctx.sim.get_mut(locs[1]).unwrap().path.0.neighbors |= 1 << (i as u8); + randomize_offset = true; + } else if !self.bridges.contains_key(&locs[1]) { + //dbg!("here"); called 18 times + let center = (locs[1] + locs[2]) / 2; + let id = + establish_site(self, &mut ctx.reseed(), center, move |place| { + Site { + kind: SiteKind::Bridge(locs[1], locs[2]), + site_tmp: None, + center, + place, + } + }); + self.bridges.insert(locs[1], (locs[2], id)); + self.bridges.insert(locs[2], (locs[1], id)); + } + /* + let to_prev_idx = NEIGHBORS + .iter() + .enumerate() + .find(|(_, dir)| **dir == (locs[0] - locs[1]).map(|e| e.signum())) + .expect("Track locations must be neighbors") + .0; + + let to_next_idx = NEIGHBORS + .iter() + .enumerate() + .find(|(_, dir)| **dir == (locs[2] - locs[1]).map(|e| e.signum())) + .expect("Track locations must be neighbors") + .0; + + ctx.sim.get_mut(locs[0]).unwrap().path.0.neighbors |= + 1 << ((to_prev_idx as u8 + 4) % 8); + ctx.sim.get_mut(locs[2]).unwrap().path.0.neighbors |= + 1 << ((to_next_idx as u8 + 4) % 8); + let mut chunk = ctx.sim.get_mut(locs[1]).unwrap(); + chunk.path.0.neighbors |= + (1 << (to_prev_idx as u8)) | (1 << (to_next_idx as u8)); + */ + if randomize_offset { + let mut chunk = ctx.sim.get_mut(locs[1]).unwrap(); + chunk.path.0.offset = + Vec2::new(ctx.rng.gen_range(-16..17), ctx.rng.gen_range(-16..17)); + } } + + // Take note of the track + let track = self.tracks.insert(Track { cost, path }); + self.track_map + .entry(site) + .or_default() + .insert(nearby, track); } } } @@ -1341,30 +1331,18 @@ fn find_path( let sim = &ctx.sim; // NOTE: If heuristic overestimates the actual cost, then A* is not guaranteed // to produce the least-cost path (since it will explore partially based on - // the heuristic). TODO: heuristic can be larger than actual cost, since - // diagonals can only cost `1.0` if a path exists and since bridges have - // zero cost (and cover multiple tiles). + // the heuristic). + // TODO: heuristic can be larger than actual cost, since existing bridges cost + // 1.0 (after the 1.0 that is added to everthting), but they can cover + // multiple chunks. let heuristic = move |l: &Vec2, _: &Vec2| (l.distance_squared(b) as f32).sqrt(); let neighbors = |l: &Vec2| { let l = *l; let bridge = get_bridge(l); - /* - NEIGHBORS - .iter() - .filter_map(move |dir| walk_in_dir(sim, bridge, l, *dir)) - */ - /* - */ - // Using walk_in_all_dirs saves ~500 ms let potential = walk_in_all_dirs(sim, bridge, l); - potential.into_iter().filter_map(|p| p) - }; - // transition cost? - let transition = |a: &Vec2, b: &Vec2| { - // factoring this out: 7463 ms -> 7356 ms - let bridge = get_bridge(*a); - 1.0 + walk_in_dir(sim, bridge, *a, (*b - *a).map(|e| e.signum())) - .map_or(10000.0, |(_, cost)| cost) + potential + .into_iter() + .filter_map(|p| p.map(|(node, cost)| (node, cost + 1.0))) }; let satisfied = |l: &Vec2| *l == b; let cluster = |l: &Vec2| { @@ -1384,21 +1362,17 @@ fn find_path( BuildHasherDefault::::default(), ); astar - .poll( - MAX_PATH_ITERS, - heuristic, - neighbors, - transition, - satisfied, - cluster, - ) + .poll(MAX_PATH_ITERS, heuristic, neighbors, satisfied, cluster) .into_path() .and_then(|path| astar.get_cheapest_cost().map(|cost| (path, cost))) } -use core::sync::atomic::{AtomicUsize, Ordering}; -static CC: AtomicUsize = AtomicUsize::new(0); - +/// Return Some if travel between a location and a chunk next to it is permitted +/// If permitted, the approximate relative const of traversal is given +// (TODO: by whom?) +/// Return tuple: (final location, cost) +/// +/// For efficiency, this computes for all 8 directions at once. fn walk_in_all_dirs( sim: &WorldSim, bridge: Option>, @@ -1407,9 +1381,10 @@ fn walk_in_all_dirs( let mut potential = [None; 8]; let mut adjacents = [a; 8]; - for i in 0..8 { - adjacents[i] += NEIGHBORS[i]; - } + NEIGHBORS + .iter() + .zip(&mut adjacents) + .for_each(|(&dir, adj)| *adj += dir); let Some(a_chunk) = sim.get(a) else { return potential }; let mut chunks = [None; 8]; @@ -1418,6 +1393,7 @@ fn walk_in_all_dirs( chunks[i] = sim.get(adjacents[i]); } } + for i in 0..8 { let Some(b_chunk) = chunks[i] else { continue }; @@ -1436,7 +1412,7 @@ fn walk_in_all_dirs( // Look for potential bridge spots in the cardinal directions if // `loc_suitable_for_wallking` was false for the adjacent chunk. for i in 0..4 { - // These happen to be the dirs where: dir.x == 0 || dir.y == 0 + // These happen to be indices for dirs where: dir.x == 0 || dir.y == 0 let i = i * 2; if potential[i].is_none() { let dir = NEIGHBORS[i]; @@ -1463,50 +1439,9 @@ fn walk_in_all_dirs( potential } -/// Return Some if travel between a location and a chunk next to it is permitted -/// If permitted, the approximate relative const of traversal is given -// (TODO: by whom?) -// -// Return tuple: (final location, cost) -fn walk_in_dir( - sim: &WorldSim, - // Is there a bridge at `a`? - bridge: Option>, - a: Vec2, - dir: Vec2, -) -> Option<(Vec2, f32)> { - //CC.fetch_add(1, Ordering::Relaxed); - if let Some(p) = bridge.filter(|p| (p - a).map(|e| e.signum()) == dir) { - // Traversing an existing bridge has no cost. - Some((p, 0.0)) - } else if loc_suitable_for_walking(sim, a + dir) { - let a_chunk = sim.get(a)?; - let b_chunk = sim.get(a + dir)?; - - let hill_cost = ((b_chunk.alt - a_chunk.alt).abs() / 5.0).powi(2); - - let water_cost = (b_chunk.water_alt - b_chunk.alt + 8.0).clamped(0.0, 8.0) * 3.0; // Try not to path swamps / tidal areas - let wild_cost = if b_chunk.path.0.is_way() { - 0.0 // Traversing existing paths has no additional cost! - } else { - 3.0 // + (1.0 - b_chunk.tree_density) * 20.0 // Prefer going through forests, for aesthetics - }; - Some((a + dir, 1.0 + hill_cost + water_cost + wild_cost)) - } else if dir.x == 0 || dir.y == 0 { - // if we can skip over unsuitable area with a bridge - (4..=5).find_map(|i| { - loc_suitable_for_walking(sim, a + dir * i) - .then(|| (a + dir * i, 120.0 + (i - 4) as f32 * 10.0)) - }) - } else { - None - } -} - /// Return true if a position is suitable for walking on fn loc_suitable_for_walking(sim: &WorldSim, loc: Vec2) -> bool { if sim.get(loc).is_some() { - // 7181 ms -> 6868 ms (300 ms! almost 10% of pathfinding time) !NEIGHBORS .iter() .map(|n| { diff --git a/world/src/sim/erosion.rs b/world/src/sim/erosion.rs index 751b5f7f33..31d5d8adfa 100644 --- a/world/src/sim/erosion.rs +++ b/world/src/sim/erosion.rs @@ -124,7 +124,6 @@ pub enum RiverKind { impl RiverKind { pub fn is_ocean(&self) -> bool { matches!(*self, RiverKind::Ocean) } - #[inline(always)] // saves ~100 ms on current `world_generate_time` pub fn is_river(&self) -> bool { matches!(*self, RiverKind::River { .. }) } pub fn is_lake(&self) -> bool { matches!(*self, RiverKind::Lake { .. }) } @@ -213,11 +212,7 @@ impl RiverData { pub fn near_river(&self) -> bool { self.is_river() || !self.neighbor_rivers.is_empty() } - pub fn near_water(&self) -> bool { - // 7408 ms -> 7270 ms (only 50 ms difference now) - self.river_kind.is_some() || !self.neighbor_rivers.is_empty() - //self.near_river() || self.is_lake() || self.is_ocean() - } + pub fn near_water(&self) -> bool { self.near_river() || self.is_lake() || self.is_ocean() } } /// Draw rivers and assign them heights, widths, and velocities. Take some