stepper: refactoring

* put `Error` as an associated type into separate trait
* make `Step::finish` generic over O instead of Step
* introduce `PollStep` trait including `finish` method
* use `PollStep` in GUI, fix infinitely repeating errors
* replace {Activity,Invoker,Interaction}Status with Poll<String>
* `PollStep` provides `Step<_, Poll<_>>`

Fixes #78.
This commit is contained in:
Alain Emilia Anna Zscheile 2024-10-04 19:53:27 +02:00
parent f02b7be878
commit 4171443c45
12 changed files with 188 additions and 181 deletions

View File

@ -9,7 +9,7 @@ use crate::{
drawing::{band::BandTermsegIndex, graph::PrimitiveIndex},
geometry::primitive::PrimitiveShape,
router::{navmesh::Navmesh, route::RouteStepper, trace::TraceStepper, Router, RouterStatus},
stepper::Step,
stepper::{Step, StepError},
};
use super::{
@ -86,9 +86,11 @@ impl AutorouteExecutionStepper {
}
}
impl<M: AccessMesadata> Step<Autorouter<M>, AutorouteStatus, AutorouterError, ()>
for AutorouteExecutionStepper
{
impl StepError for AutorouteExecutionStepper {
type Error = AutorouterError;
}
impl<M: AccessMesadata> Step<Autorouter<M>, AutorouteStatus> for AutorouteExecutionStepper {
fn step(&mut self, autorouter: &mut Autorouter<M>) -> Result<AutorouteStatus, AutorouterError> {
let Some(curr_ratline) = self.curr_ratline else {
return Ok(AutorouteStatus::Finished);

View File

@ -11,7 +11,7 @@ use crate::{
geometry::{primitive::PrimitiveShape, shape::MeasureLength},
graph::MakeRef,
router::{navmesh::Navmesh, trace::TraceStepper},
stepper::Step,
stepper::{Step, StepError},
};
use super::{
@ -66,9 +66,13 @@ impl CompareDetoursExecutionStepper {
}
}
impl StepError for CompareDetoursExecutionStepper {
type Error = AutorouterError;
}
// XXX: Do we really need this to be a stepper? We don't use at the moment, as sorting functions
// aren't steppable either. It may be useful for debugging later on tho.
impl<M: AccessMesadata> Step<Autorouter<M>, CompareDetoursStatus, AutorouterError, (f64, f64)>
impl<M: AccessMesadata> Step<Autorouter<M>, CompareDetoursStatus>
for CompareDetoursExecutionStepper
{
fn step(

View File

@ -1,12 +1,17 @@
use core::task::Poll;
use enum_dispatch::enum_dispatch;
use serde::{Deserialize, Serialize};
use crate::{board::mesadata::AccessMesadata, layout::via::ViaWeight, stepper::Step};
use crate::{
board::mesadata::AccessMesadata,
layout::via::ViaWeight,
stepper::{PollStep, Step, StepError},
};
use super::{
autoroute::{AutorouteExecutionStepper, AutorouteStatus},
compare_detours::{CompareDetoursExecutionStepper, CompareDetoursStatus},
invoker::{Invoker, InvokerError, InvokerStatus},
invoker::{Invoker, InvokerError},
measure_length::MeasureLengthExecutionStepper,
place_via::PlaceViaExecutionStepper,
remove_bands::RemoveBandsExecutionStepper,
@ -38,30 +43,28 @@ impl ExecutionStepper {
fn step_catch_err<M: AccessMesadata>(
&mut self,
invoker: &mut Invoker<M>,
) -> Result<InvokerStatus, InvokerError> {
Ok(match self {
) -> Poll<Result<String, InvokerError>> {
match self {
ExecutionStepper::Autoroute(autoroute) => {
match autoroute.step(&mut invoker.autorouter)? {
AutorouteStatus::Running => InvokerStatus::Running,
AutorouteStatus::Routed(..) => InvokerStatus::Running,
AutorouteStatus::Finished => {
InvokerStatus::Finished("finished autorouting".to_string())
}
AutorouteStatus::Running => Poll::Pending,
AutorouteStatus::Routed(..) => Poll::Pending,
AutorouteStatus::Finished => Poll::Ready("finished autorouting".to_string()),
}
}
ExecutionStepper::PlaceVia(place_via) => {
place_via.doit(&mut invoker.autorouter)?;
InvokerStatus::Finished("finished placing via".to_string())
Poll::Ready("finished placing via".to_string())
}
ExecutionStepper::RemoveBands(remove_bands) => {
remove_bands.doit(&mut invoker.autorouter)?;
InvokerStatus::Finished("finished removing bands".to_string())
Poll::Ready("finished removing bands".to_string())
}
ExecutionStepper::CompareDetours(compare_detours) => {
match compare_detours.step(&mut invoker.autorouter)? {
CompareDetoursStatus::Running => InvokerStatus::Running,
CompareDetoursStatus::Running => Poll::Pending,
CompareDetoursStatus::Finished(total_length1, total_length2) => {
InvokerStatus::Finished(format!(
Poll::Ready(format!(
"total detour lengths are {} and {}",
total_length1, total_length2
))
@ -70,27 +73,30 @@ impl ExecutionStepper {
}
ExecutionStepper::MeasureLength(measure_length) => {
let length = measure_length.doit(&mut invoker.autorouter)?;
InvokerStatus::Finished(format!("Total length of selected bands: {}", length))
Poll::Ready(format!("Total length of selected bands: {}", length))
}
})
}
.map(Ok)
}
}
impl<M: AccessMesadata> Step<Invoker<M>, InvokerStatus, InvokerError, ()> for ExecutionStepper {
fn step(&mut self, invoker: &mut Invoker<M>) -> Result<InvokerStatus, InvokerError> {
match self.step_catch_err(invoker) {
Ok(InvokerStatus::Running) => Ok(InvokerStatus::Running),
Ok(InvokerStatus::Finished(msg)) => {
impl StepError for ExecutionStepper {
type Error = InvokerError;
}
impl<M: AccessMesadata> PollStep<Invoker<M>, String> for ExecutionStepper {
fn poll_step(&mut self, invoker: &mut Invoker<M>) -> Poll<Result<String, InvokerError>> {
self.step_catch_err(invoker).map(|x| match x {
Ok(msg) => {
if let Some(command) = invoker.ongoing_command.take() {
invoker.history.do_(command);
}
Ok(InvokerStatus::Finished(msg))
Ok(msg)
}
Err(err) => {
invoker.ongoing_command = None;
Err(err)
}
}
})
}
}

View File

@ -1,8 +1,7 @@
//! Manages the execution of routing commands within the autorouting system.
use std::cmp::Ordering;
use contracts_try::debug_requires;
use core::cmp::Ordering;
use enum_dispatch::enum_dispatch;
use thiserror::Error;
@ -11,7 +10,7 @@ use crate::{
drawing::graph::PrimitiveIndex,
geometry::primitive::PrimitiveShape,
router::{navmesh::Navmesh, trace::TraceStepper},
stepper::Step,
stepper::{PollStep, Step},
};
use super::{
@ -59,16 +58,6 @@ pub enum InvokerError {
Autorouter(#[from] AutorouterError),
}
impl TryInto<()> for InvokerStatus {
type Error = ();
fn try_into(self) -> Result<(), ()> {
match self {
InvokerStatus::Running => Err(()),
InvokerStatus::Finished(..) => Ok(()),
}
}
}
pub struct Invoker<M: AccessMesadata> {
pub(super) autorouter: Autorouter<M>,
pub(super) history: History,
@ -93,20 +82,11 @@ impl<M: AccessMesadata> Invoker<M> {
}
//#[debug_requires(self.ongoing_command.is_none())]
pub fn execute(&mut self, command: Command) -> Result<(), InvokerError> {
pub fn execute(&mut self, command: Command) -> Result<String, InvokerError> {
let mut execute = self.execute_stepper(command)?;
loop {
let status = match execute.step(self) {
Ok(status) => status,
Err(err) => return Err(err),
};
if let InvokerStatus::Finished(..) = status {
let res = PollStep::finish(&mut execute, self)?;
self.history.set_undone(std::iter::empty());
return Ok(());
}
}
Ok(res)
}
#[debug_requires(self.ongoing_command.is_none())]
@ -178,17 +158,8 @@ impl<M: AccessMesadata> Invoker<M> {
pub fn redo(&mut self) -> Result<(), InvokerError> {
let command = self.history.last_undone()?.clone();
let mut execute = self.execute_stepper(command)?;
loop {
let status = match execute.step(self) {
Ok(status) => status,
Err(err) => return Err(err),
};
if let InvokerStatus::Finished(..) = status {
return Ok(self.history.redo()?);
}
}
PollStep::finish(&mut execute, self)?;
Ok(self.history.redo()?)
}
#[debug_requires(self.ongoing_command.is_none())]

View File

@ -1,63 +1,25 @@
use core::task::Poll;
use thiserror::Error;
use topola::{
autorouter::{
execution::ExecutionStepper,
invoker::{
GetGhosts, GetMaybeNavmesh, GetMaybeTrace, GetObstacles, Invoker, InvokerError,
InvokerStatus,
},
invoker::{GetGhosts, GetMaybeNavmesh, GetMaybeTrace, GetObstacles, Invoker, InvokerError},
},
board::mesadata::AccessMesadata,
drawing::graph::PrimitiveIndex,
geometry::primitive::PrimitiveShape,
router::{navmesh::Navmesh, trace::TraceStepper},
specctra::mesadata::SpecctraMesadata,
stepper::{Abort, Step},
stepper::{Abort, PollStep, StepError},
};
use crate::interaction::{
InteractionContext, InteractionError, InteractionStatus, InteractionStepper,
};
use crate::interaction::{InteractionContext, InteractionError, InteractionStepper};
pub struct ActivityContext<'a> {
pub interaction: InteractionContext,
pub invoker: &'a mut Invoker<SpecctraMesadata>,
}
#[derive(Debug, Clone)]
pub enum ActivityStatus {
Running,
Finished(String),
}
impl From<InteractionStatus> for ActivityStatus {
fn from(status: InteractionStatus) -> Self {
match status {
InteractionStatus::Running => ActivityStatus::Running,
InteractionStatus::Finished(msg) => ActivityStatus::Finished(msg),
}
}
}
impl From<InvokerStatus> for ActivityStatus {
fn from(status: InvokerStatus) -> Self {
match status {
InvokerStatus::Running => ActivityStatus::Running,
InvokerStatus::Finished(msg) => ActivityStatus::Finished(msg),
}
}
}
impl TryInto<()> for ActivityStatus {
type Error = ();
fn try_into(self) -> Result<(), ()> {
match self {
ActivityStatus::Running => Err(()),
ActivityStatus::Finished(..) => Ok(()),
}
}
}
#[derive(Error, Debug, Clone)]
pub enum ActivityError {
#[error(transparent)]
@ -71,13 +33,22 @@ pub enum ActivityStepper {
Execution(ExecutionStepper),
}
impl Step<ActivityContext<'_>, ActivityStatus, ActivityError, ()> for ActivityStepper {
fn step(&mut self, context: &mut ActivityContext) -> Result<ActivityStatus, ActivityError> {
match self {
ActivityStepper::Interaction(interaction) => {
Ok(interaction.step(&mut context.interaction)?.into())
impl StepError for ActivityStepper {
type Error = ActivityError;
}
ActivityStepper::Execution(execution) => Ok(execution.step(context.invoker)?.into()),
impl PollStep<ActivityContext<'_>, String> for ActivityStepper {
fn poll_step(
&mut self,
context: &mut ActivityContext<'_>,
) -> Poll<Result<String, ActivityError>> {
match self {
ActivityStepper::Interaction(interaction) => interaction
.poll_step(&mut context.interaction)
.map(|x| x.map_err(Into::into)),
ActivityStepper::Execution(execution) => execution
.poll_step(context.invoker)
.map(|x| x.map_err(Into::into)),
}
}
}
@ -86,9 +57,11 @@ impl Abort<ActivityContext<'_>> for ActivityStepper {
fn abort(&mut self, context: &mut ActivityContext) {
match self {
ActivityStepper::Interaction(interaction) => {
Ok(interaction.abort(&mut context.interaction))
interaction.abort(&mut context.interaction);
}
ActivityStepper::Execution(execution) => {
PollStep::finish(execution, context.invoker); // TODO.
}
ActivityStepper::Execution(execution) => execution.finish(context.invoker), // TODO.
};
}
}
@ -135,7 +108,7 @@ impl GetObstacles for ActivityStepper {
pub struct ActivityStepperWithStatus {
activity: ActivityStepper,
maybe_status: Option<ActivityStatus>,
maybe_status: Option<Poll<String>>,
}
impl ActivityStepperWithStatus {
@ -146,22 +119,33 @@ impl ActivityStepperWithStatus {
}
}
pub fn maybe_status(&self) -> Option<ActivityStatus> {
pub fn maybe_status(&self) -> Option<Poll<String>> {
self.maybe_status.clone()
}
}
impl Step<ActivityContext<'_>, ActivityStatus, ActivityError, ()> for ActivityStepperWithStatus {
fn step(&mut self, context: &mut ActivityContext) -> Result<ActivityStatus, ActivityError> {
let status = self.activity.step(context)?;
self.maybe_status = Some(status.clone());
Ok(status.into())
impl StepError for ActivityStepperWithStatus {
type Error = ActivityError;
}
impl PollStep<ActivityContext<'_>, String> for ActivityStepperWithStatus {
fn poll_step(
&mut self,
context: &mut ActivityContext<'_>,
) -> Poll<Result<String, ActivityError>> {
let res = self.activity.poll_step(context);
self.maybe_status = match &res {
Poll::Pending => Some(Poll::Pending),
Poll::Ready(Ok(msg)) => Some(Poll::Ready(msg.clone())),
Poll::Ready(Err(_)) => None,
};
res
}
}
impl Abort<ActivityContext<'_>> for ActivityStepperWithStatus {
fn abort(&mut self, context: &mut ActivityContext) {
self.maybe_status = Some(ActivityStatus::Finished(String::from("aborted")));
fn abort(&mut self, context: &mut ActivityContext<'_>) {
self.maybe_status = Some(Poll::Ready("aborted".to_string()));
self.activity.abort(context);
}
}

View File

@ -6,6 +6,7 @@ use std::{
mpsc::{channel, Receiver, Sender},
Arc, Mutex,
},
task::Poll,
};
use unic_langid::{langid, LanguageIdentifier};
@ -15,11 +16,11 @@ use topola::{
design::{LoadingError as SpecctraLoadingError, SpecctraDesign},
mesadata::SpecctraMesadata,
},
stepper::Step,
stepper::PollStep,
};
use crate::{
activity::{ActivityContext, ActivityStatus, ActivityStepperWithStatus},
activity::{ActivityContext, ActivityStepperWithStatus},
config::Config,
error_dialog::ErrorDialog,
interaction::InteractionContext,
@ -171,15 +172,17 @@ impl App {
}
if let Some(ref mut activity) = self.maybe_activity {
return match activity.step(&mut ActivityContext {
return match activity.poll_step(&mut ActivityContext {
interaction: InteractionContext {},
invoker,
}) {
Ok(ActivityStatus::Running) => true,
Ok(ActivityStatus::Finished(..)) => false,
Err(err) => {
Poll::Pending => true,
Poll::Ready(res) => {
if let Err(err) = res {
self.error_dialog
.push_error("tr-module-invoker", format!("{}", err));
}
self.maybe_activity = None;
false
}
};

View File

@ -1,10 +1,11 @@
use core::task::Poll;
use thiserror::Error;
use topola::{
autorouter::invoker::{GetGhosts, GetMaybeNavmesh, GetMaybeTrace, GetObstacles},
drawing::graph::PrimitiveIndex,
geometry::primitive::PrimitiveShape,
router::{navmesh::Navmesh, trace::TraceStepper},
stepper::{Abort, Step},
stepper::{Abort, PollStep, StepError},
};
use crate::activity::ActivityStepperWithStatus;
@ -15,22 +16,6 @@ pub struct InteractionContext {
// (we will need an additional struct to hold a reference to a `Board<...>`)
}
#[derive(Debug, Clone)]
pub enum InteractionStatus {
Running,
Finished(String),
}
impl TryInto<()> for InteractionStatus {
type Error = ();
fn try_into(self) -> Result<(), ()> {
match self {
InteractionStatus::Running => Err(()),
InteractionStatus::Finished(..) => Ok(()),
}
}
}
#[derive(Error, Debug, Clone)]
pub enum InteractionError {
#[error("nothing to interact with")]
@ -44,12 +29,16 @@ pub enum InteractionStepper {
// - interactively moving a footprint.
}
impl Step<InteractionContext, InteractionStatus, InteractionError, ()> for InteractionStepper {
fn step(
impl StepError for InteractionStepper {
type Error = InteractionError;
}
impl PollStep<InteractionContext, String> for InteractionStepper {
fn poll_step(
&mut self,
invoker: &mut InteractionContext,
) -> Result<InteractionStatus, InteractionError> {
Ok(InteractionStatus::Finished(String::from("")))
) -> Poll<Result<String, InteractionError>> {
Poll::Ready(Ok(String::new()))
}
}

View File

@ -1,6 +1,7 @@
use std::{
path::Path,
sync::{mpsc::Sender, Arc, Mutex},
task::Poll,
};
use topola::{
@ -20,7 +21,7 @@ use topola::{
use crate::{
action::{Action, Switch, Trigger},
activity::{ActivityContext, ActivityStatus, ActivityStepperWithStatus},
activity::{ActivityContext, ActivityStepperWithStatus},
app::{execute, handle_file},
interaction::InteractionContext,
overlay::Overlay,
@ -340,7 +341,7 @@ impl MenuBar {
}
} else if remove_bands.consume_key_triggered(ctx, ui) {
if maybe_activity.as_mut().map_or(true, |activity| {
matches!(activity.maybe_status(), Some(ActivityStatus::Finished(..)))
matches!(activity.maybe_status(), Some(Poll::Ready(..)))
}) {
if let (Some(invoker), Some(ref mut overlay)) = (
arc_mutex_maybe_invoker.lock().unwrap().as_mut(),
@ -357,7 +358,7 @@ impl MenuBar {
} else if place_via.consume_key_enabled(ctx, ui, &mut self.is_placing_via) {
} else if autoroute.consume_key_triggered(ctx, ui) {
if maybe_activity.as_mut().map_or(true, |activity| {
matches!(activity.maybe_status(), Some(ActivityStatus::Finished(..)))
matches!(activity.maybe_status(), Some(Poll::Ready(..)))
}) {
if let (Some(invoker), Some(ref mut overlay)) = (
arc_mutex_maybe_invoker.lock().unwrap().as_mut(),
@ -374,7 +375,7 @@ impl MenuBar {
}
} else if compare_detours.consume_key_triggered(ctx, ui) {
if maybe_activity.as_mut().map_or(true, |activity| {
matches!(activity.maybe_status(), Some(ActivityStatus::Finished(..)))
matches!(activity.maybe_status(), Some(Poll::Ready(..)))
}) {
if let (Some(invoker), Some(ref mut overlay)) = (
arc_mutex_maybe_invoker.lock().unwrap().as_mut(),
@ -391,7 +392,7 @@ impl MenuBar {
}
} else if measure_length.consume_key_triggered(ctx, ui) {
if maybe_activity.as_mut().map_or(true, |activity| {
matches!(activity.maybe_status(), Some(ActivityStatus::Finished(..)))
matches!(activity.maybe_status(), Some(Poll::Ready(..)))
}) {
if let (Some(invoker), Some(ref mut overlay)) = (
arc_mutex_maybe_invoker.lock().unwrap().as_mut(),

View File

@ -1,8 +1,5 @@
use crate::{
activity::{ActivityStatus, ActivityStepperWithStatus},
translator::Translator,
viewport::Viewport,
};
use crate::{activity::ActivityStepperWithStatus, translator::Translator, viewport::Viewport};
use core::task::Poll;
pub struct StatusBar {}
@ -22,10 +19,10 @@ impl StatusBar {
let latest_pos = viewport.transform.inverse()
* ctx.input(|i| i.pointer.latest_pos().unwrap_or_default());
let mut message = String::from("");
let mut message = String::new();
if let Some(activity) = maybe_activity {
if let Some(ActivityStatus::Finished(msg)) = activity.maybe_status() {
if let Some(Poll::Ready(msg)) = activity.maybe_status() {
message = msg;
}
}

View File

@ -15,7 +15,7 @@ use thiserror::Error;
use std::cmp::Ordering;
use crate::stepper::Step;
use crate::stepper::{Step, StepError};
#[derive(Copy, Clone, Debug)]
pub struct MinScored<K, T>(pub K, pub T);
@ -203,8 +203,17 @@ where
}
}
impl<G, K, R, S: AstarStrategy<G, K, R>>
Step<S, AstarStatus<G, K, R>, AstarError, (K, Vec<G::NodeId>, R)> for Astar<G, K>
impl<G, K> StepError for Astar<G, K>
where
G: GraphBase,
G::NodeId: Eq + Hash,
for<'a> &'a G: IntoEdges<NodeId = G::NodeId, EdgeId = G::EdgeId> + MakeEdgeRef,
K: Measure + Copy,
{
type Error = AstarError;
}
impl<G, K, R, S: AstarStrategy<G, K, R>> Step<S, AstarStatus<G, K, R>> for Astar<G, K>
where
G: GraphBase,
G::NodeId: Eq + Hash,

View File

@ -1,7 +1,5 @@
use crate::{
drawing::{
band::BandTermsegIndex, dot::FixedDotIndex, graph::PrimitiveIndex, rules::AccessRules,
},
drawing::{dot::FixedDotIndex, graph::PrimitiveIndex, rules::AccessRules},
geometry::primitive::PrimitiveShape,
router::{
astar::{Astar, AstarError, AstarStatus},
@ -10,7 +8,7 @@ use crate::{
tracer::Tracer,
Router, RouterAstarStrategy, RouterStatus,
},
stepper::Step,
stepper::{Step, StepError},
};
pub struct RouteStepper {
@ -73,9 +71,11 @@ impl RouteStepper {
}
}
impl<'a, R: AccessRules> Step<Router<'a, R>, RouterStatus, AstarError, BandTermsegIndex>
for RouteStepper
{
impl StepError for RouteStepper {
type Error = AstarError;
}
impl<'a, R: AccessRules> Step<Router<'a, R>, RouterStatus> for RouteStepper {
fn step(&mut self, router: &mut Router<R>) -> Result<RouterStatus, AstarError> {
let tracer = Tracer::new(router.layout_mut());
let target = self.astar.graph.destination();

View File

@ -1,7 +1,16 @@
pub trait Step<C, S: TryInto<O>, E, O> {
fn step(&mut self, context: &mut C) -> Result<S, E>;
use core::task::Poll;
fn finish(&mut self, context: &mut C) -> Result<O, E> {
pub trait StepError {
type Error;
}
pub trait Step<C, S>: StepError {
fn step(&mut self, context: &mut C) -> Result<S, Self::Error>;
fn finish<O>(&mut self, context: &mut C) -> Result<O, Self::Error>
where
S: TryInto<O>,
{
loop {
if let Ok(outcome) = self.step(context)?.try_into() {
return Ok(outcome);
@ -10,8 +19,40 @@ pub trait Step<C, S: TryInto<O>, E, O> {
}
}
pub trait StepBack<C, S, E> {
fn step_back(&mut self, context: &mut C) -> Result<S, E>;
// Note that PollStep's `S` is usually not the same as Step's `S`.
pub trait PollStep<C, S>: StepError {
fn poll_step(&mut self, context: &mut C) -> Poll<Result<S, Self::Error>>;
fn finish(&mut self, context: &mut C) -> Result<S, Self::Error> {
loop {
if let Poll::Ready(outcome) = self.poll_step(context) {
return outcome;
}
}
}
}
/*
impl<C, S, Stepper: Step<C, Poll<S>>> PollStep<C, S> for Stepper {
#[inline]
fn poll_step(&mut self, context: &mut C) -> Poll<Result<S, Self::Error>> {
self.step(context)?.map(Ok)
}
}
*/
impl<C, S, Stepper: PollStep<C, S>> Step<C, Poll<S>> for Stepper {
#[inline]
fn step(&mut self, context: &mut C) -> Result<Poll<S>, Self::Error> {
Ok(match self.poll_step(context) {
Poll::Pending => Poll::Pending,
Poll::Ready(x) => Poll::Ready(x?),
})
}
}
pub trait StepBack<C, S>: StepError {
fn step_back(&mut self, context: &mut C) -> Result<S, Self::Error>;
}
pub trait Abort<C> {