feat(topola-egui): Add progress bar for the currently routed ratline

The capability to measure progress will later be useful to choose slower
but better optimization strategies if more time is available.
This commit is contained in:
Mikolaj Wielgus 2025-07-09 11:45:15 +02:00
parent 29dc59df04
commit 68d9844d0d
15 changed files with 233 additions and 43 deletions

View File

@ -4,7 +4,7 @@
use std::ops::ControlFlow; use std::ops::ControlFlow;
use topola::interactor::activity::ActivityStepperWithStatus; use topola::{interactor::activity::ActivityStepperWithStatus, stepper::EstimateProgress};
use crate::{translator::Translator, viewport::Viewport}; use crate::{translator::Translator, viewport::Viewport};
@ -38,6 +38,20 @@ impl StatusBar {
"x: {} y: {} \t {}", "x: {} y: {} \t {}",
latest_pos.x, -latest_pos.y, message latest_pos.x, -latest_pos.y, message
)); ));
if let Some(activity) = maybe_activity {
let value = activity.estimate_progress_value();
let maximum = activity.estimate_progress_maximum();
ui.add(
egui::ProgressBar::new((value / maximum) as f32).text(format!(
"{:.1} ({:.1}/{:.1})",
value / maximum * 100.0,
value,
maximum
)),
);
}
}); });
} }
} }

View File

@ -376,7 +376,6 @@ impl Viewport {
}; };
if menu_bar.show_pathfinding_scores { if menu_bar.show_pathfinding_scores {
//TODO "{astar.scores[index]} ({astar.estimate_scores[index]}) (...)"
let score_text = thetastar let score_text = thetastar
.scores() .scores()
.get(&navnode) .get(&navnode)
@ -384,7 +383,7 @@ impl Viewport {
format!("g={:.2}", s) format!("g={:.2}", s)
}); });
let estimate_score_text = thetastar let estimate_score_text = thetastar
.estimate_scores() .cost_to_goal_estimate_scores()
.get(&navnode) .get(&navnode)
.map_or_else(String::new, |s| { .map_or_else(String::new, |s| {
format!("(f={:.2})", s) format!("(f={:.2})", s)

View File

@ -17,7 +17,7 @@ use crate::{
router::{ router::{
navcord::Navcord, navmesh::Navmesh, thetastar::ThetastarStepper, RouteStepper, Router, navcord::Navcord, navmesh::Navmesh, thetastar::ThetastarStepper, RouteStepper, Router,
}, },
stepper::Step, stepper::{EstimateProgress, Step},
}; };
use super::{invoker::GetDebugOverlayData, Autorouter, AutorouterError, AutorouterOptions}; use super::{invoker::GetDebugOverlayData, Autorouter, AutorouterError, AutorouterOptions};
@ -164,6 +164,22 @@ impl<M: AccessMesadata> Step<Autorouter<M>, Option<LayoutEdit>, AutorouteContinu
} }
} }
impl EstimateProgress for AutorouteExecutionStepper {
type Value = f64;
fn estimate_progress_value(&self) -> f64 {
self.route
.as_ref()
.map_or(0.0, |route| route.estimate_progress_value())
}
fn estimate_progress_maximum(&self) -> f64 {
self.route
.as_ref()
.map_or(0.0, |route| route.estimate_progress_maximum())
}
}
impl GetDebugOverlayData for AutorouteExecutionStepper { impl GetDebugOverlayData for AutorouteExecutionStepper {
fn maybe_thetastar(&self) -> Option<&ThetastarStepper<Navmesh, f64>> { fn maybe_thetastar(&self) -> Option<&ThetastarStepper<Navmesh, f64>> {
self.route.as_ref().map(|route| route.thetastar()) self.route.as_ref().map(|route| route.thetastar())

View File

@ -15,7 +15,7 @@ use crate::{
geometry::{primitive::PrimitiveShape, shape::MeasureLength}, geometry::{primitive::PrimitiveShape, shape::MeasureLength},
graph::MakeRef, graph::MakeRef,
router::{navcord::Navcord, navmesh::Navmesh, thetastar::ThetastarStepper}, router::{navcord::Navcord, navmesh::Navmesh, thetastar::ThetastarStepper},
stepper::Step, stepper::{EstimateProgress, Step},
}; };
use super::{ use super::{
@ -100,6 +100,10 @@ impl<M: AccessMesadata> Step<Autorouter<M>, (f64, f64)> for CompareDetoursExecut
} }
} }
impl EstimateProgress for CompareDetoursExecutionStepper {
type Value = f64;
}
impl GetDebugOverlayData for CompareDetoursExecutionStepper { impl GetDebugOverlayData for CompareDetoursExecutionStepper {
fn maybe_thetastar(&self) -> Option<&ThetastarStepper<Navmesh, f64>> { fn maybe_thetastar(&self) -> Option<&ThetastarStepper<Navmesh, f64>> {
self.autoroute.maybe_thetastar() self.autoroute.maybe_thetastar()

View File

@ -11,7 +11,7 @@ use crate::{
board::AccessMesadata, board::AccessMesadata,
layout::{via::ViaWeight, LayoutEdit}, layout::{via::ViaWeight, LayoutEdit},
router::ng, router::ng,
stepper::{Abort, Step}, stepper::{Abort, EstimateProgress, Step},
}; };
use super::{ use super::{
@ -165,3 +165,39 @@ impl<M: AccessMesadata + Clone> Abort<Invoker<M>> for ExecutionStepper<M> {
} }
} }
} }
// Since enum_dispatch does not really support generics, we implement this the
// long way.
impl<M> EstimateProgress for ExecutionStepper<M> {
type Value = f64;
fn estimate_progress_value(&self) -> f64 {
match self {
ExecutionStepper::Autoroute(autoroute) => autoroute.estimate_progress_value(),
ExecutionStepper::TopoAutoroute(toporoute) => toporoute.estimate_progress_value(),
ExecutionStepper::PlaceVia(place_via) => place_via.estimate_progress_value(),
ExecutionStepper::RemoveBands(remove_bands) => remove_bands.estimate_progress_value(),
ExecutionStepper::CompareDetours(compare_detours) => {
compare_detours.estimate_progress_value()
}
ExecutionStepper::MeasureLength(measure_length) => {
measure_length.estimate_progress_value()
}
}
}
fn estimate_progress_maximum(&self) -> f64 {
match self {
ExecutionStepper::Autoroute(autoroute) => autoroute.estimate_progress_maximum(),
ExecutionStepper::TopoAutoroute(toporoute) => toporoute.estimate_progress_maximum(),
ExecutionStepper::PlaceVia(place_via) => place_via.estimate_progress_maximum(),
ExecutionStepper::RemoveBands(remove_bands) => remove_bands.estimate_progress_maximum(),
ExecutionStepper::CompareDetours(compare_detours) => {
compare_detours.estimate_progress_maximum()
}
ExecutionStepper::MeasureLength(measure_length) => {
measure_length.estimate_progress_maximum()
}
}
}
}

View File

@ -150,10 +150,10 @@ impl<M: AccessMesadata + Clone> Invoker<M> {
} }
} }
#[debug_requires(self.ongoing_command.is_none())]
/// Pass given command to be executed. /// Pass given command to be executed.
/// ///
/// Function used to set given [`Command`] to ongoing state, dispatch and execute it. /// Function used to set given [`Command`] to ongoing state, dispatch and execute it.
#[debug_requires(self.ongoing_command.is_none())]
pub fn execute_stepper( pub fn execute_stepper(
&mut self, &mut self,
command: Command, command: Command,

View File

@ -8,6 +8,7 @@
use crate::{ use crate::{
board::AccessMesadata, geometry::shape::MeasureLength as MeasureLengthTrait, graph::MakeRef, board::AccessMesadata, geometry::shape::MeasureLength as MeasureLengthTrait, graph::MakeRef,
stepper::EstimateProgress,
}; };
use super::{invoker::GetDebugOverlayData, selection::BandSelection, Autorouter, AutorouterError}; use super::{invoker::GetDebugOverlayData, selection::BandSelection, Autorouter, AutorouterError};
@ -47,4 +48,7 @@ impl MeasureLengthExecutionStepper {
} }
} }
impl EstimateProgress for MeasureLengthExecutionStepper {
type Value = f64;
}
impl GetDebugOverlayData for MeasureLengthExecutionStepper {} impl GetDebugOverlayData for MeasureLengthExecutionStepper {}

View File

@ -9,6 +9,7 @@
use crate::{ use crate::{
board::AccessMesadata, board::AccessMesadata,
layout::{via::ViaWeight, LayoutEdit}, layout::{via::ViaWeight, LayoutEdit},
stepper::EstimateProgress,
}; };
use super::{invoker::GetDebugOverlayData, Autorouter, AutorouterError}; use super::{invoker::GetDebugOverlayData, Autorouter, AutorouterError};
@ -46,4 +47,7 @@ impl PlaceViaExecutionStepper {
} }
} }
impl EstimateProgress for PlaceViaExecutionStepper {
type Value = f64;
}
impl GetDebugOverlayData for PlaceViaExecutionStepper {} impl GetDebugOverlayData for PlaceViaExecutionStepper {}

View File

@ -4,7 +4,7 @@
//! Provides functionality to remove bands from the layout. //! Provides functionality to remove bands from the layout.
use crate::{board::AccessMesadata, layout::LayoutEdit}; use crate::{board::AccessMesadata, layout::LayoutEdit, stepper::EstimateProgress};
use super::{invoker::GetDebugOverlayData, selection::BandSelection, Autorouter, AutorouterError}; use super::{invoker::GetDebugOverlayData, selection::BandSelection, Autorouter, AutorouterError};
@ -44,4 +44,7 @@ impl RemoveBandsExecutionStepper {
} }
} }
impl EstimateProgress for RemoveBandsExecutionStepper {
type Value = f64;
}
impl GetDebugOverlayData for RemoveBandsExecutionStepper {} impl GetDebugOverlayData for RemoveBandsExecutionStepper {}

View File

@ -25,7 +25,7 @@ use crate::{
ng, ng,
thetastar::ThetastarStepper, thetastar::ThetastarStepper,
}, },
stepper::{Abort, OnEvent, Step}, stepper::{Abort, EstimateProgress, OnEvent, Step},
}; };
/// Stores the interactive input data from the user. /// Stores the interactive input data from the user.
@ -98,6 +98,26 @@ impl<M: AccessMesadata + Clone> Abort<Invoker<M>> for ActivityStepper<M> {
} }
} }
// Since enum_dispatch does not really support generics, we implement this the
// long way.
impl<M> EstimateProgress for ActivityStepper<M> {
type Value = f64;
fn estimate_progress_value(&self) -> f64 {
match self {
ActivityStepper::Interaction(..) => 0.0,
ActivityStepper::Execution(execution) => execution.estimate_progress_value(),
}
}
fn estimate_progress_maximum(&self) -> f64 {
match self {
ActivityStepper::Interaction(..) => 0.0,
ActivityStepper::Execution(execution) => execution.estimate_progress_maximum(),
}
}
}
impl<M: AccessMesadata> OnEvent<ActivityContext<'_, M>, InteractiveEvent> for ActivityStepper<M> { impl<M: AccessMesadata> OnEvent<ActivityContext<'_, M>, InteractiveEvent> for ActivityStepper<M> {
type Output = Result<(), InteractionError>; type Output = Result<(), InteractionError>;
@ -179,6 +199,18 @@ impl<M: AccessMesadata + Clone> OnEvent<ActivityContext<'_, M>, InteractiveEvent
} }
} }
impl<M> EstimateProgress for ActivityStepperWithStatus<M> {
type Value = f64;
fn estimate_progress_value(&self) -> f64 {
self.activity.estimate_progress_value()
}
fn estimate_progress_maximum(&self) -> f64 {
self.activity.estimate_progress_maximum()
}
}
impl<M> GetDebugOverlayData for ActivityStepperWithStatus<M> { impl<M> GetDebugOverlayData for ActivityStepperWithStatus<M> {
fn maybe_thetastar(&self) -> Option<&ThetastarStepper<Navmesh, f64>> { fn maybe_thetastar(&self) -> Option<&ThetastarStepper<Navmesh, f64>> {
self.activity.maybe_thetastar() self.activity.maybe_thetastar()

View File

@ -15,7 +15,7 @@ use crate::{
geometry::primitive::PrimitiveShape, geometry::primitive::PrimitiveShape,
graph::GenericIndex, graph::GenericIndex,
layout::{poly::PolyWeight, Layout, LayoutEdit}, layout::{poly::PolyWeight, Layout, LayoutEdit},
stepper::Abort, stepper::{Abort, EstimateProgress},
}; };
use super::{ use super::{
@ -232,6 +232,10 @@ impl<R: AccessRules + Clone + std::panic::RefUnwindSafe> AutorouteExecutionStepp
} }
} }
impl<M> EstimateProgress for AutorouteExecutionStepper<M> {
type Value = f64;
}
impl<M> GetDebugOverlayData for AutorouteExecutionStepper<M> { impl<M> GetDebugOverlayData for AutorouteExecutionStepper<M> {
fn maybe_topo_navmesh(&self) -> Option<pie::navmesh::NavmeshRef<'_, super::PieNavmeshBase>> { fn maybe_topo_navmesh(&self) -> Option<pie::navmesh::NavmeshRef<'_, super::PieNavmeshBase>> {
Some(pie::navmesh::NavmeshRef { Some(pie::navmesh::NavmeshRef {

View File

@ -19,7 +19,7 @@ use crate::{
thetastar::{ThetastarError, ThetastarStepper}, thetastar::{ThetastarError, ThetastarStepper},
Router, RouterThetastarStrategy, Router, RouterThetastarStrategy,
}, },
stepper::Step, stepper::{EstimateProgress, Step},
}; };
#[derive(Getters, Dissolve)] #[derive(Getters, Dissolve)]
@ -80,6 +80,7 @@ impl<R: AccessRules> Step<Router<'_, R>, BandTermsegIndex> for RouteStepper {
let target = self.thetastar.graph().destination(); let target = self.thetastar.graph().destination();
let mut strategy = RouterThetastarStrategy::new(layout, &mut self.navcord, target); let mut strategy = RouterThetastarStrategy::new(layout, &mut self.navcord, target);
let result = self.thetastar.step(&mut strategy); let result = self.thetastar.step(&mut strategy);
self.ghosts = strategy.probe_ghosts; self.ghosts = strategy.probe_ghosts;
self.obstacles = strategy.probe_obstacles; self.obstacles = strategy.probe_obstacles;
@ -97,3 +98,15 @@ impl<R: AccessRules> Step<Router<'_, R>, BandTermsegIndex> for RouteStepper {
} }
} }
} }
impl EstimateProgress for RouteStepper {
type Value = f64;
fn estimate_progress_value(&self) -> f64 {
self.thetastar.estimate_progress_value()
}
fn estimate_progress_maximum(&self) -> f64 {
self.thetastar.estimate_progress_maximum()
}
}

View File

@ -119,7 +119,7 @@ impl<R: AccessRules> ThetastarStrategy<Navmesh, f64, BandTermsegIndex>
self.navcord.step_back(self.layout); self.navcord.step_back(self.layout);
} }
fn estimate_cost(&mut self, navmesh: &Navmesh, vertex: NavnodeIndex) -> f64 { fn estimate_cost_to_goal(&mut self, navmesh: &Navmesh, vertex: NavnodeIndex) -> f64 {
let start_point = PrimitiveIndex::from(navmesh.node_weight(vertex).unwrap().node) let start_point = PrimitiveIndex::from(navmesh.node_weight(vertex).unwrap().node)
.primitive(self.layout.drawing()) .primitive(self.layout.drawing())
.shape() .shape()

View File

@ -11,7 +11,7 @@
use std::collections::btree_map::Entry; use std::collections::btree_map::Entry;
use std::collections::{BTreeMap, BinaryHeap}; use std::collections::{BTreeMap, BinaryHeap};
use std::ops::ControlFlow; use std::ops::{ControlFlow, Sub};
use derive_getters::Getters; use derive_getters::Getters;
use petgraph::algo::Measure; use petgraph::algo::Measure;
@ -20,7 +20,7 @@ use thiserror::Error;
use std::cmp::Ordering; use std::cmp::Ordering;
use crate::stepper::Step; use crate::stepper::{EstimateProgress, Step};
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug)]
pub struct MinScored<K, T>(pub K, pub T); pub struct MinScored<K, T>(pub K, pub T);
@ -122,7 +122,7 @@ where
) -> Result<Option<R>, ()>; ) -> Result<Option<R>, ()>;
fn place_probe_to_navnode<'a>(&mut self, graph: &'a G, probed_navnode: G::NodeId) -> Option<K>; fn place_probe_to_navnode<'a>(&mut self, graph: &'a G, probed_navnode: G::NodeId) -> Option<K>;
fn remove_probe(&mut self, graph: &G); fn remove_probe(&mut self, graph: &G);
fn estimate_cost(&mut self, graph: &G, navnode: G::NodeId) -> K; fn estimate_cost_to_goal(&mut self, graph: &G, navnode: G::NodeId) -> K;
} }
pub trait MakeEdgeRef: IntoEdgeReferences { pub trait MakeEdgeRef: IntoEdgeReferences {
@ -155,21 +155,28 @@ where
G: GraphBase, G: GraphBase,
G::NodeId: Eq + Ord, G::NodeId: Eq + Ord,
for<'a> &'a G: IntoEdges<NodeId = G::NodeId, EdgeId = G::EdgeId> + MakeEdgeRef, for<'a> &'a G: IntoEdges<NodeId = G::NodeId, EdgeId = G::EdgeId> + MakeEdgeRef,
K: Measure + Copy, K: Measure + Copy + Sub<Output = K>,
{ {
state: ThetastarState<G::NodeId, G::EdgeId>, state: ThetastarState<G::NodeId, G::EdgeId>,
graph: G, graph: G,
/// The priority queue of the navnodes to expand.
#[getter(skip)] #[getter(skip)]
visit_next: BinaryHeap<MinScored<K, G::NodeId>>, frontier: BinaryHeap<MinScored<K, G::NodeId>>,
/// Also known as the g-scores, or just g. /// Also known as the g-scores, or just g.
scores: BTreeMap<G::NodeId, K>, scores: BTreeMap<G::NodeId, K>,
/// Also known as the f-scores, or just f. /// Also known as the f-scores, or just f.
estimate_scores: BTreeMap<G::NodeId, K>, cost_to_goal_estimate_scores: BTreeMap<G::NodeId, K>,
#[getter(skip)] #[getter(skip)]
path_tracker: PathTracker<G>, path_tracker: PathTracker<G>,
// FIXME: To work around edge references borrowing from the graph we collect then reiterate over them. // FIXME: To work around edge references borrowing from the graph we collect then reiterate over them.
#[getter(skip)] #[getter(skip)]
edge_ids: Vec<G::EdgeId>, edge_ids: Vec<G::EdgeId>,
#[getter(skip)]
progress_estimate_value: K,
#[getter(skip)]
progress_estimate_maximum: K,
} }
#[derive(Error, Debug, Clone)] #[derive(Error, Debug, Clone)]
@ -183,29 +190,55 @@ where
G: GraphBase, G: GraphBase,
G::NodeId: Eq + Ord, G::NodeId: Eq + Ord,
for<'a> &'a G: IntoEdges<NodeId = G::NodeId, EdgeId = G::EdgeId> + MakeEdgeRef, for<'a> &'a G: IntoEdges<NodeId = G::NodeId, EdgeId = G::EdgeId> + MakeEdgeRef,
K: Measure + Copy, K: Measure + Copy + Sub<Output = K>,
{ {
pub fn new<R>( pub fn new<R>(
graph: G, graph: G,
start: G::NodeId, start: G::NodeId,
strategy: &mut impl ThetastarStrategy<G, K, R>, strategy: &mut impl ThetastarStrategy<G, K, R>,
) -> Self { ) -> Self {
let estimated_cost_from_start_to_goal = strategy.estimate_cost_to_goal(&graph, start);
let mut this = Self { let mut this = Self {
state: ThetastarState::Scanning, state: ThetastarState::Scanning,
graph, graph,
visit_next: BinaryHeap::new(), frontier: BinaryHeap::new(),
scores: BTreeMap::new(), scores: BTreeMap::new(),
estimate_scores: BTreeMap::new(), cost_to_goal_estimate_scores: BTreeMap::new(),
path_tracker: PathTracker::<G>::new(), path_tracker: PathTracker::<G>::new(),
edge_ids: Vec::new(), edge_ids: Vec::new(),
progress_estimate_value: K::default(),
progress_estimate_maximum: estimated_cost_from_start_to_goal,
}; };
let zero_score = K::default(); let zero_score = K::default();
this.scores.insert(start, zero_score); this.scores.insert(start, zero_score);
this.visit_next this.frontier
.push(MinScored(strategy.estimate_cost(&this.graph, start), start)); .push(MinScored(estimated_cost_from_start_to_goal, start));
this this
} }
fn push_to_frontier<R>(
&mut self,
next: G::NodeId,
next_score: K,
predecessor: G::NodeId,
strategy: &mut impl ThetastarStrategy<G, K, R>,
) {
let cost_to_goal_estimate = strategy.estimate_cost_to_goal(&self.graph, next);
if cost_to_goal_estimate > self.progress_estimate_maximum {
self.progress_estimate_maximum = cost_to_goal_estimate;
}
if self.progress_estimate_maximum - cost_to_goal_estimate > self.progress_estimate_value {
self.progress_estimate_value = self.progress_estimate_maximum - cost_to_goal_estimate;
}
self.path_tracker.set_predecessor(next, predecessor);
let next_estimate_score = next_score + cost_to_goal_estimate;
self.frontier.push(MinScored(next_estimate_score, next));
}
} }
impl<G, K, R, S: ThetastarStrategy<G, K, R>> impl<G, K, R, S: ThetastarStrategy<G, K, R>>
@ -214,7 +247,7 @@ where
G: GraphBase, G: GraphBase,
G::NodeId: Eq + Ord, G::NodeId: Eq + Ord,
for<'a> &'a G: IntoEdges<NodeId = G::NodeId, EdgeId = G::EdgeId> + MakeEdgeRef, for<'a> &'a G: IntoEdges<NodeId = G::NodeId, EdgeId = G::EdgeId> + MakeEdgeRef,
K: Measure + Copy, K: Measure + Copy + Sub<Output = K>,
{ {
type Error = ThetastarError; type Error = ThetastarError;
@ -227,7 +260,7 @@ where
> { > {
match self.state { match self.state {
ThetastarState::Scanning => { ThetastarState::Scanning => {
let Some(MinScored(estimate_score, navnode)) = self.visit_next.pop() else { let Some(MinScored(estimate_score, navnode)) = self.frontier.pop() else {
return Err(ThetastarError::NotFound); return Err(ThetastarError::NotFound);
}; };
@ -243,7 +276,7 @@ where
return Ok(ControlFlow::Break((cost, path, result))); return Ok(ControlFlow::Break((cost, path, result)));
} }
match self.estimate_scores.entry(navnode) { match self.cost_to_goal_estimate_scores.entry(navnode) {
Entry::Occupied(mut entry) => { Entry::Occupied(mut entry) => {
// If the node has already been visited with an equal or lower // If the node has already been visited with an equal or lower
// estimated score than now, then we do not need to re-visit it. // estimated score than now, then we do not need to re-visit it.
@ -299,10 +332,7 @@ where
} }
} }
self.path_tracker.set_predecessor(next, parent_navnode); self.push_to_frontier(next, next_score, parent_navnode, strategy);
let next_estimate_score =
next_score + strategy.estimate_cost(&self.graph, next);
self.visit_next.push(MinScored(next_estimate_score, next));
self.state = ThetastarState::Probing(visited_navnode); self.state = ThetastarState::Probing(visited_navnode);
Ok(ControlFlow::Continue(self.state)) Ok(ControlFlow::Continue(self.state))
@ -353,10 +383,7 @@ where
} }
} }
self.path_tracker.set_predecessor(next, visited_navnode); self.push_to_frontier(next, next_score, visited_navnode, strategy);
let next_estimate_score =
next_score + strategy.estimate_cost(&self.graph, next);
self.visit_next.push(MinScored(next_estimate_score, next));
self.state = ThetastarState::Probing(visited_navnode); self.state = ThetastarState::Probing(visited_navnode);
Ok(ControlFlow::Continue(self.state)) Ok(ControlFlow::Continue(self.state))
@ -374,3 +401,21 @@ where
} }
} }
} }
impl<G, K> EstimateProgress for ThetastarStepper<G, K>
where
G: GraphBase,
G::NodeId: Eq + Ord,
for<'a> &'a G: IntoEdges<NodeId = G::NodeId, EdgeId = G::EdgeId> + MakeEdgeRef,
K: Measure + Copy + Sub<Output = K>,
{
type Value = K;
fn estimate_progress_value(&self) -> K {
self.progress_estimate_value
}
fn estimate_progress_maximum(&self) -> K {
self.progress_estimate_maximum
}
}

View File

@ -11,14 +11,14 @@ use core::ops::ControlFlow;
/// ///
/// An object that implements this trait is called a "stepper". /// An object that implements this trait is called a "stepper".
/// ///
/// Steppers always progress linearly, that is, it is presumed that the context /// Steppers always progress linearly and their future states are determined by
/// does not change between calls in a way that can affect the stepper's future /// the initial state. It is assumed that the changes in context cannot change
/// states. Advanceable data structures designed for uses where the future state /// the stepper's execution. Advanceable data structures designed for uses where
/// intentionally *may* change from the information supplied as arguments are /// the future state intentionally *may* change from the information supplied
/// not considered steppers. An example of such an advanceable non-stepper is /// after initialization are not considered steppers. An example of such an
/// the [`Navcord`](crate::router::navcord::Navcord) struct, as it does not progress /// advanceable non-stepper is the [`Navcord`] (crate::router::navcord::Navcord)
/// linearly because it branches out by on each call taking in a /// struct, as it does not progress linearly because it branches out on each
/// changeable `to` argument that affects the future states. /// call by taking in a changeable `to` argument that affects the future states.
/// ///
/// Petgraph's counterpart of this trait is its /// Petgraph's counterpart of this trait is its
/// [`petgraph::visit::Walker<Context>`] trait. /// [`petgraph::visit::Walker<Context>`] trait.
@ -53,9 +53,25 @@ pub trait Abort<C> {
fn abort(&mut self, context: &mut C); fn abort(&mut self, context: &mut C);
} }
/// Steppers that may receive discrete events and act on them, implement this trait. /// Steppers that can receive discrete events and act on them, implement this
/// trait.
// XXX: Doesn't this violate the rule that stepper's future states are
// determined by its initial state?
pub trait OnEvent<Ctx, Event> { pub trait OnEvent<Ctx, Event> {
type Output; type Output;
fn on_event(&mut self, context: &mut Ctx, event: Event) -> Self::Output; fn on_event(&mut self, context: &mut Ctx, event: Event) -> Self::Output;
} }
/// Some steppers report estimates of how far they are from completion.
pub trait EstimateProgress {
type Value: Default;
fn estimate_progress_value(&self) -> Self::Value {
Self::Value::default()
}
fn estimate_progress_maximum(&self) -> Self::Value {
Self::Value::default()
}
}