mirror of https://codeberg.org/topola/topola.git
router: make A* a walker, i.e. make it runnable in a while loop
This commit is contained in:
parent
194383d9b3
commit
499564e6cf
|
|
@ -56,7 +56,7 @@ impl Autoroute {
|
|||
Some(this)
|
||||
}
|
||||
|
||||
pub fn next<R: RulesTrait>(
|
||||
pub fn step<R: RulesTrait>(
|
||||
&mut self,
|
||||
autorouter: &mut Autorouter<R>,
|
||||
observer: &mut impl RouterObserverTrait<R>,
|
||||
|
|
@ -140,7 +140,7 @@ impl<R: RulesTrait> Autorouter<R> {
|
|||
|
||||
pub fn autoroute(&mut self, selection: &Selection, observer: &mut impl RouterObserverTrait<R>) {
|
||||
if let Some(mut autoroute) = self.autoroute_walk(selection) {
|
||||
while autoroute.next(self, observer) {
|
||||
while autoroute.step(self, observer) {
|
||||
//
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ impl Execute {
|
|||
observer: &mut impl RouterObserverTrait<R>,
|
||||
) -> bool {
|
||||
match self {
|
||||
Execute::Autoroute(autoroute) => autoroute.next(&mut invoker.autorouter, observer),
|
||||
Execute::Autoroute(autoroute) => autoroute.step(&mut invoker.autorouter, observer),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -276,7 +276,7 @@ fn main() -> Result<(), anyhow::Error> {
|
|||
|
||||
let mut autorouter = Autorouter::new(layout.clone()).unwrap();
|
||||
if let Some(mut autoroute) = autorouter.autoroute_walk(&Selection::new()) {
|
||||
while autoroute.next(
|
||||
while autoroute.step(
|
||||
&mut autorouter,
|
||||
&mut DebugRouterObserver::new(
|
||||
&mut event_pump,
|
||||
|
|
|
|||
|
|
@ -105,6 +105,110 @@ where
|
|||
fn estimate_cost(&mut self, node: G::NodeId) -> K;
|
||||
}
|
||||
|
||||
struct Astar<G, K>
|
||||
where
|
||||
G: IntoEdges,
|
||||
G::NodeId: Eq + Hash,
|
||||
K: Measure + Copy,
|
||||
{
|
||||
pub graph: G,
|
||||
pub visit_next: BinaryHeap<MinScored<K, G::NodeId>>,
|
||||
pub scores: HashMap<G::NodeId, K>,
|
||||
pub estimate_scores: HashMap<G::NodeId, K>,
|
||||
pub path_tracker: PathTracker<G>,
|
||||
}
|
||||
|
||||
enum AstarStatus<G, K, R>
|
||||
where
|
||||
G: IntoEdges,
|
||||
G::NodeId: Eq + Hash,
|
||||
K: Measure + Copy,
|
||||
{
|
||||
Running,
|
||||
Success(K, Vec<G::NodeId>, R),
|
||||
Failure,
|
||||
}
|
||||
|
||||
impl<G, K> Astar<G, K>
|
||||
where
|
||||
G: IntoEdges,
|
||||
G::NodeId: Eq + Hash,
|
||||
K: Measure + Copy,
|
||||
{
|
||||
pub fn new<R>(graph: G, start: G::NodeId, strategy: &mut impl AstarStrategy<G, K, R>) -> Self {
|
||||
let mut this = Self {
|
||||
graph,
|
||||
visit_next: BinaryHeap::new(),
|
||||
scores: HashMap::new(),
|
||||
estimate_scores: HashMap::new(),
|
||||
path_tracker: PathTracker::<G>::new(),
|
||||
};
|
||||
|
||||
let zero_score = K::default();
|
||||
this.scores.insert(start, zero_score);
|
||||
this.visit_next
|
||||
.push(MinScored(strategy.estimate_cost(start), start));
|
||||
this
|
||||
}
|
||||
|
||||
pub fn step<R>(&mut self, strategy: &mut impl AstarStrategy<G, K, R>) -> AstarStatus<G, K, R> {
|
||||
let Some(MinScored(estimate_score, node)) = self.visit_next.pop() else {
|
||||
return AstarStatus::Failure;
|
||||
};
|
||||
|
||||
if let Some(result) = strategy.is_goal(node, &self.path_tracker) {
|
||||
let path = self.path_tracker.reconstruct_path_to(node);
|
||||
let cost = self.scores[&node];
|
||||
return AstarStatus::Success(cost, path, result);
|
||||
}
|
||||
|
||||
// This lookup can be unwrapped without fear of panic since the node was
|
||||
// necessarily scored before adding it to `visit_next`.
|
||||
let node_score = self.scores[&node];
|
||||
|
||||
match self.estimate_scores.entry(node) {
|
||||
Occupied(mut entry) => {
|
||||
// If the node has already been visited with an equal or lower score than
|
||||
// now, then we do not need to re-visit it.
|
||||
if *entry.get() <= estimate_score {
|
||||
return AstarStatus::Running;
|
||||
}
|
||||
entry.insert(estimate_score);
|
||||
}
|
||||
Vacant(entry) => {
|
||||
entry.insert(estimate_score);
|
||||
}
|
||||
}
|
||||
|
||||
for edge in self.graph.edges(node) {
|
||||
if let Some(edge_cost) = strategy.edge_cost(edge) {
|
||||
let next = edge.target();
|
||||
let next_score = node_score + edge_cost;
|
||||
|
||||
match self.scores.entry(next) {
|
||||
Occupied(mut entry) => {
|
||||
// No need to add neighbors that we have already reached through a
|
||||
// shorter path than now.
|
||||
if *entry.get() <= next_score {
|
||||
return AstarStatus::Running;
|
||||
}
|
||||
entry.insert(next_score);
|
||||
}
|
||||
Vacant(entry) => {
|
||||
entry.insert(next_score);
|
||||
}
|
||||
}
|
||||
|
||||
self.path_tracker.set_predecessor(next, node);
|
||||
let next_estimate_score = next_score + strategy.estimate_cost(next);
|
||||
self.visit_next.push(MinScored(next_estimate_score, next));
|
||||
}
|
||||
}
|
||||
|
||||
AstarStatus::Running
|
||||
}
|
||||
}
|
||||
|
||||
pub fn astar<G, K, R>(
|
||||
graph: G,
|
||||
start: G::NodeId,
|
||||
|
|
@ -115,64 +219,10 @@ where
|
|||
G::NodeId: Eq + Hash,
|
||||
K: Measure + Copy,
|
||||
{
|
||||
let mut visit_next = BinaryHeap::new();
|
||||
let mut scores = HashMap::new(); // g-values, cost to reach the node
|
||||
let mut estimate_scores = HashMap::new(); // f-values, cost to reach + estimate cost to goal
|
||||
let mut path_tracker = PathTracker::<G>::new();
|
||||
let mut astar = Astar::new(graph, start, strategy);
|
||||
|
||||
let zero_score = K::default();
|
||||
scores.insert(start, zero_score);
|
||||
visit_next.push(MinScored(strategy.estimate_cost(start), start));
|
||||
|
||||
while let Some(MinScored(estimate_score, node)) = visit_next.pop() {
|
||||
if let Some(result) = strategy.is_goal(node, &path_tracker) {
|
||||
let path = path_tracker.reconstruct_path_to(node);
|
||||
let cost = scores[&node];
|
||||
return Some((cost, path, result));
|
||||
}
|
||||
|
||||
// This lookup can be unwrapped without fear of panic since the node was
|
||||
// necessarily scored before adding it to `visit_next`.
|
||||
let node_score = scores[&node];
|
||||
|
||||
match estimate_scores.entry(node) {
|
||||
Occupied(mut entry) => {
|
||||
// If the node has already been visited with an equal or lower score than
|
||||
// now, then we do not need to re-visit it.
|
||||
if *entry.get() <= estimate_score {
|
||||
continue;
|
||||
}
|
||||
entry.insert(estimate_score);
|
||||
}
|
||||
Vacant(entry) => {
|
||||
entry.insert(estimate_score);
|
||||
}
|
||||
}
|
||||
|
||||
for edge in graph.edges(node) {
|
||||
if let Some(edge_cost) = strategy.edge_cost(edge) {
|
||||
let next = edge.target();
|
||||
let next_score = node_score + edge_cost;
|
||||
|
||||
match scores.entry(next) {
|
||||
Occupied(mut entry) => {
|
||||
// No need to add neighbors that we have already reached through a
|
||||
// shorter path than now.
|
||||
if *entry.get() <= next_score {
|
||||
continue;
|
||||
}
|
||||
entry.insert(next_score);
|
||||
}
|
||||
Vacant(entry) => {
|
||||
entry.insert(next_score);
|
||||
}
|
||||
}
|
||||
|
||||
path_tracker.set_predecessor(next, node);
|
||||
let next_estimate_score = next_score + strategy.estimate_cost(next);
|
||||
visit_next.push(MinScored(next_estimate_score, next));
|
||||
}
|
||||
}
|
||||
while let AstarStatus::Running = astar.step(strategy) {
|
||||
//
|
||||
}
|
||||
|
||||
None
|
||||
|
|
|
|||
Loading…
Reference in New Issue