mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Additional optimizations (such as avoiding calling find_path when we know we won't use the result or that it will fail), cleanup of excess notes and commented code, probably other misc optimizations
This commit is contained in:
parent
1f5ebbd100
commit
81885fe8e5
@ -11,9 +11,8 @@ use std::collections::BinaryHeap;
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct PathEntry<S> {
|
||||
// cost so far + heursitic
|
||||
priority: f32,
|
||||
cost_estimate: f32,
|
||||
node: S,
|
||||
//cost: f32,
|
||||
}
|
||||
|
||||
impl<S: Eq> PartialEq for PathEntry<S> {
|
||||
@ -26,7 +25,10 @@ impl<S: Eq> Ord for PathEntry<S> {
|
||||
// This method implements reverse ordering, so that the lowest cost
|
||||
// will be ordered first
|
||||
fn cmp(&self, other: &PathEntry<S>) -> 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<S: Eq> PartialOrd for PathEntry<S> {
|
||||
// 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<S>) -> bool { other.priority <= self.priority }
|
||||
fn le(&self, other: &PathEntry<S>) -> bool { other.cost_estimate <= self.cost_estimate }
|
||||
}
|
||||
|
||||
pub enum PathResult<T> {
|
||||
@ -78,32 +80,29 @@ struct NodeEntry<S> {
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct Cluster<S> {
|
||||
// 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<S>; 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<S, Hasher> {
|
||||
iter: usize,
|
||||
max_iters: usize,
|
||||
potential_nodes: BinaryHeap<PathEntry<S>>, // 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<S, NodeEntry<S>, Hasher>,
|
||||
// -> 25055 ms -> 15771 ms with Box -> fixed bugs 10731 ms, hmmm
|
||||
clusters: HashMap<S, Box<Cluster<S>>, Hasher>, // TODO: Box cluster?
|
||||
//came_from: HashMap<S, S, Hasher>,
|
||||
//cheapest_scores: HashMap<S, f32, Hasher>,
|
||||
//final_scores: HashMap<S, f32, Hasher>,
|
||||
//visited: HashSet<S, Hasher>,
|
||||
clusters: HashMap<S, Box<Cluster<S>>, Hasher>,
|
||||
start_node: S,
|
||||
cheapest_node: Option<S>,
|
||||
cheapest_cost: Option<f32>,
|
||||
@ -120,29 +119,10 @@ impl<S: Clone + Eq + Hash, H: BuildHasher + Clone> Astar<S, H> {
|
||||
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<S: Clone + Eq + Hash, H: BuildHasher + Clone> Astar<S, H> {
|
||||
pub fn poll<I>(
|
||||
&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<S>
|
||||
where
|
||||
// Combining transition into this: 9913 ms -> 8204 ms (~1.7 out of ~6.5 seconds)
|
||||
I: Iterator<Item = (S, f32)>,
|
||||
I: Iterator<Item = (S, f32)>, // (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<S: Clone + Eq + Hash, H: BuildHasher + Clone> Astar<S, H> {
|
||||
}),
|
||||
);
|
||||
}
|
||||
*/
|
||||
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<S: Clone + Eq + Hash, H: BuildHasher + Clone> Astar<S, H> {
|
||||
)
|
||||
})
|
||||
.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<S: Clone + Eq + Hash, H: BuildHasher + Clone> Astar<S, H> {
|
||||
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<S: Clone + Eq + Hash, H: BuildHasher + Clone> Astar<S, H> {
|
||||
.is_some();
|
||||
self.cheapest_scores.insert(neighbor.clone(), cost);
|
||||
*/
|
||||
/*
|
||||
let previously_visited = self
|
||||
.visited_nodes
|
||||
.insert(neighbor.clone(), NodeEntry {
|
||||
@ -301,7 +237,7 @@ impl<S: Clone + Eq + Hash, H: BuildHasher + Clone> Astar<S, H> {
|
||||
cheapest_score: cost,
|
||||
})
|
||||
.is_some();
|
||||
*/
|
||||
/*
|
||||
let cluster_mut =
|
||||
self.clusters.entry(cluster_key).or_insert_with(|| {
|
||||
Box::new(Cluster {
|
||||
@ -312,37 +248,28 @@ impl<S: Clone + Eq + Hash, H: BuildHasher + Clone> Astar<S, H> {
|
||||
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<S: Clone + Eq + Hash, H: BuildHasher + Clone> Astar<S, H> {
|
||||
|
||||
pub fn get_cheapest_cost(&self) -> Option<f32> { 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<S> {
|
||||
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()
|
||||
}
|
||||
|
@ -55,16 +55,10 @@ pub struct Civs {
|
||||
/// (3) we have 8-byte keys (for which FxHash is fastest).
|
||||
pub track_map: DHashMap<Id<Site>, DHashMap<Id<Site>, Id<Track>>>,
|
||||
|
||||
// 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<i32>,
|
||||
(Vec2<i32>, Id<Site>),
|
||||
//std::hash::BuildHasherDefault<siphasher::sip::SipHasher13>,
|
||||
std::hash::BuildHasherDefault<fxhash::FxHasher64>,
|
||||
//std::hash::BuildHasherDefault<fxhash::FxHasher>,
|
||||
//std::hash::BuildHasherDefault<fxhash::FxHasher32>, // too many collisions!
|
||||
>,
|
||||
|
||||
pub sites: Store<Site>,
|
||||
@ -527,8 +521,6 @@ impl Civs {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dbg!(CC.load(Ordering::Relaxed));
|
||||
}
|
||||
|
||||
// TODO: Move this
|
||||
@ -1120,7 +1112,6 @@ impl Civs {
|
||||
loc: Vec2<i32>,
|
||||
site_fn: impl FnOnce(Id<Place>) -> Site,
|
||||
) -> Id<Site> {
|
||||
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::<Vec<_>>();
|
||||
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<i32>, _: &Vec2<i32>| (l.distance_squared(b) as f32).sqrt();
|
||||
let neighbors = |l: &Vec2<i32>| {
|
||||
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<i32>, b: &Vec2<i32>| {
|
||||
// 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<i32>| *l == b;
|
||||
let cluster = |l: &Vec2<i32>| {
|
||||
@ -1384,21 +1362,17 @@ fn find_path(
|
||||
BuildHasherDefault::<FxHasher64>::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<Vec2<i32>>,
|
||||
@ -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<Vec2<i32>>,
|
||||
a: Vec2<i32>,
|
||||
dir: Vec2<i32>,
|
||||
) -> Option<(Vec2<i32>, 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<i32>) -> bool {
|
||||
if sim.get(loc).is_some() {
|
||||
// 7181 ms -> 6868 ms (300 ms! almost 10% of pathfinding time)
|
||||
!NEIGHBORS
|
||||
.iter()
|
||||
.map(|n| {
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user