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)
|
Some(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn next<R: RulesTrait>(
|
pub fn step<R: RulesTrait>(
|
||||||
&mut self,
|
&mut self,
|
||||||
autorouter: &mut Autorouter<R>,
|
autorouter: &mut Autorouter<R>,
|
||||||
observer: &mut impl RouterObserverTrait<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>) {
|
pub fn autoroute(&mut self, selection: &Selection, observer: &mut impl RouterObserverTrait<R>) {
|
||||||
if let Some(mut autoroute) = self.autoroute_walk(selection) {
|
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>,
|
observer: &mut impl RouterObserverTrait<R>,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
match self {
|
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();
|
let mut autorouter = Autorouter::new(layout.clone()).unwrap();
|
||||||
if let Some(mut autoroute) = autorouter.autoroute_walk(&Selection::new()) {
|
if let Some(mut autoroute) = autorouter.autoroute_walk(&Selection::new()) {
|
||||||
while autoroute.next(
|
while autoroute.step(
|
||||||
&mut autorouter,
|
&mut autorouter,
|
||||||
&mut DebugRouterObserver::new(
|
&mut DebugRouterObserver::new(
|
||||||
&mut event_pump,
|
&mut event_pump,
|
||||||
|
|
|
||||||
|
|
@ -105,6 +105,110 @@ where
|
||||||
fn estimate_cost(&mut self, node: G::NodeId) -> K;
|
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>(
|
pub fn astar<G, K, R>(
|
||||||
graph: G,
|
graph: G,
|
||||||
start: G::NodeId,
|
start: G::NodeId,
|
||||||
|
|
@ -115,64 +219,10 @@ where
|
||||||
G::NodeId: Eq + Hash,
|
G::NodeId: Eq + Hash,
|
||||||
K: Measure + Copy,
|
K: Measure + Copy,
|
||||||
{
|
{
|
||||||
let mut visit_next = BinaryHeap::new();
|
let mut astar = Astar::new(graph, start, strategy);
|
||||||
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 zero_score = K::default();
|
while let AstarStatus::Running = astar.step(strategy) {
|
||||||
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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
None
|
None
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue