diff --git a/crates/topola-egui/src/status_bar.rs b/crates/topola-egui/src/status_bar.rs index 0815ae4..deb1780 100644 --- a/crates/topola-egui/src/status_bar.rs +++ b/crates/topola-egui/src/status_bar.rs @@ -4,7 +4,10 @@ use std::ops::ControlFlow; -use topola::{interactor::activity::ActivityStepperWithStatus, stepper::EstimateLinearProgress}; +use topola::{ + interactor::activity::ActivityStepperWithStatus, + stepper::{EstimateProgress, GetMaybeReconfigurationTriggerProgress}, +}; use crate::{translator::Translator, viewport::Viewport}; @@ -40,21 +43,32 @@ impl StatusBar { )); if let Some(activity) = maybe_activity { - let linear_progress = activity.estimate_linear_progress(); - let value = linear_progress.value(); - let maximum = linear_progress.maximum(); + let progress = activity.estimate_progress(); + let value = progress.value(); + let maximum = progress.reference(); let ratio = *value as f32 / *maximum as f32; - ui.add(egui::ProgressBar::new(ratio).text(format!( - "{:.1}% ({:.1}/{:.1})", - ratio * 100.0, - value, - maximum - ))); + if let Some(trigger_progress) = activity.reconfiguration_trigger_progress() { + ui.add(egui::ProgressBar::new(ratio).text(format!( + "{:.1}% ({:.1}/{:.1}) (sma: {:.1}, min: {:.1}))", + ratio * 100.0, + value, + maximum, + trigger_progress.value(), + trigger_progress.reference(), + ))); + } else { + ui.add(egui::ProgressBar::new(ratio).text(format!( + "{:.1}% ({:.1}/{:.1})", + ratio * 100.0, + value, + maximum + ))); + } - let linear_subprogress = linear_progress.subscale(); + let linear_subprogress = progress.subscale(); let value = linear_subprogress.value(); - let maximum = linear_subprogress.maximum(); + let maximum = linear_subprogress.reference(); let ratio = *value as f32 / *maximum as f32; ui.add(egui::ProgressBar::new(ratio).text(format!( diff --git a/crates/topola-egui/src/workspace.rs b/crates/topola-egui/src/workspace.rs index b0e2d81..568c9c5 100644 --- a/crates/topola-egui/src/workspace.rs +++ b/crates/topola-egui/src/workspace.rs @@ -39,7 +39,7 @@ pub struct Workspace { Receiver>>, ), - update_counter: f32, + dt_accumulator: f32, } impl Workspace { @@ -65,7 +65,7 @@ impl Workspace { ) })?, history_channel: channel(), - update_counter: 0.0, + dt_accumulator: 0.0, }) } @@ -82,12 +82,12 @@ impl Workspace { let instant = Instant::now(); if maybe_step_rate.is_some() { - self.update_counter += interactive_input.dt; + self.dt_accumulator += interactive_input.dt; } - while maybe_step_rate.is_none_or(|step_rate| self.update_counter >= 1.0 / step_rate) { + while maybe_step_rate.is_none_or(|step_rate| self.dt_accumulator >= 1.0 / step_rate) { if let Some(step_rate) = maybe_step_rate { - self.update_counter -= 1.0 / step_rate; + self.dt_accumulator -= 1.0 / step_rate; } if let ControlFlow::Break(()) = self.update_state(tr, error_dialog, interactive_input) { diff --git a/src/autorouter/execution.rs b/src/autorouter/execution.rs index 4250283..ce455e3 100644 --- a/src/autorouter/execution.rs +++ b/src/autorouter/execution.rs @@ -16,7 +16,7 @@ use crate::{ board::{edit::BoardEdit, AccessMesadata}, layout::via::ViaWeight, router::ng, - stepper::{Abort, EstimateLinearProgress, LinearScale, Step}, + stepper::{Abort, EstimateProgress, GetMaybeReconfigurationTriggerProgress, LinearScale, Step}, }; use super::{ @@ -173,16 +173,14 @@ impl Abort> for ExecutionStepper { // Since enum_dispatch does not really support generics, we implement this the // long way by using `match`. -impl EstimateLinearProgress for ExecutionStepper { +impl EstimateProgress for ExecutionStepper { type Value = usize; type Subscale = LinearScale; - fn estimate_linear_progress(&self) -> LinearScale> { + fn estimate_progress(&self) -> LinearScale> { match self { - ExecutionStepper::MultilayerAutoroute(autoroute) => { - autoroute.estimate_linear_progress() - } - ExecutionStepper::PlanarAutoroute(autoroute) => autoroute.estimate_linear_progress(), + ExecutionStepper::MultilayerAutoroute(autoroute) => autoroute.estimate_progress(), + ExecutionStepper::PlanarAutoroute(autoroute) => autoroute.estimate_progress(), ExecutionStepper::TopoAutoroute(..) => { LinearScale::new(0, 0, LinearScale::new(0.0, 0.0, ())) } @@ -198,3 +196,22 @@ impl EstimateLinearProgress for ExecutionStepper { } } } + +// Since enum_dispatch does not really support generics, we implement this the +// long way by using `match`. +impl GetMaybeReconfigurationTriggerProgress for ExecutionStepper { + type Subscale = (); + + fn reconfiguration_trigger_progress(&self) -> Option> { + match self { + ExecutionStepper::MultilayerAutoroute(autoroute) => { + autoroute.reconfiguration_trigger_progress() + } + ExecutionStepper::PlanarAutoroute(autoroute) => None, + ExecutionStepper::TopoAutoroute(..) => None, + ExecutionStepper::PlaceVia(..) => None, + ExecutionStepper::RemoveBands(..) => None, + ExecutionStepper::MeasureLength(..) => None, + } + } +} diff --git a/src/autorouter/multilayer_autoroute.rs b/src/autorouter/multilayer_autoroute.rs index 0aff5c0..176a4e1 100644 --- a/src/autorouter/multilayer_autoroute.rs +++ b/src/autorouter/multilayer_autoroute.rs @@ -21,9 +21,7 @@ use crate::{ drawing::graph::PrimitiveIndex, geometry::{edit::Edit, primitive::PrimitiveShape}, router::{navcord::Navcord, navmesh::Navmesh, thetastar::ThetastarStepper}, - stepper::{ - Abort, EstimateLinearProgress, LinearScale, ReconfiguratorStatus, Reconfigure, Step, - }, + stepper::{Abort, EstimateProgress, LinearScale, ReconfiguratorStatus, Reconfigure, Step}, }; #[derive(Clone, Debug)] @@ -119,12 +117,12 @@ impl Reconfigure> for MultilayerAutorouteExecut } } -impl EstimateLinearProgress for MultilayerAutorouteExecutionStepper { +impl EstimateProgress for MultilayerAutorouteExecutionStepper { type Value = usize; type Subscale = LinearScale; - fn estimate_linear_progress(&self) -> LinearScale> { - self.planar.estimate_linear_progress() + fn estimate_progress(&self) -> LinearScale> { + self.planar.estimate_progress() } } diff --git a/src/autorouter/multilayer_reconfigurator.rs b/src/autorouter/multilayer_reconfigurator.rs index 4623069..7f71a3c 100644 --- a/src/autorouter/multilayer_reconfigurator.rs +++ b/src/autorouter/multilayer_reconfigurator.rs @@ -30,7 +30,8 @@ use crate::{ geometry::primitive::PrimitiveShape, router::{navcord::Navcord, navmesh::Navmesh, thetastar::ThetastarStepper}, stepper::{ - Abort, EstimateLinearProgress, LinearScale, ReconfiguratorStatus, Reconfigure, Step, + Abort, EstimateProgress, GetMaybeReconfigurationTriggerProgress, LinearScale, + ReconfiguratorStatus, Reconfigure, SmaRateReconfigurationTrigger, Step, }, }; @@ -39,6 +40,7 @@ pub type MultilayerReconfiguratorStatus = pub struct MultilayerAutorouteReconfigurator { stepper: MultilayerAutorouteExecutionStepper, + reconfiguration_trigger: SmaRateReconfigurationTrigger, reconfigurer: MultilayerAutorouteReconfigurer, options: MultilayerAutorouteOptions, } @@ -72,6 +74,7 @@ impl MultilayerAutorouteReconfigurator { preconfiguration, options, )?, + reconfiguration_trigger: SmaRateReconfigurationTrigger::new(10, 0.5, 0.5), reconfigurer, options, }) @@ -83,6 +86,9 @@ impl MultilayerAutorouteReconfigurator { planar_result: Result, ) -> Result, MultilayerReconfiguratorStatus>, AutorouterError> { + // Reset the reconfiguration trigger. + self.reconfiguration_trigger = SmaRateReconfigurationTrigger::new(4, 0.5, 0.5); + loop { let configuration = match self .reconfigurer @@ -120,6 +126,9 @@ impl Step, Option, MultilayerReconfi autorouter: &mut Autorouter, ) -> Result, MultilayerReconfiguratorStatus>, AutorouterError> { + self.reconfiguration_trigger + .update(*self.estimate_progress().value() as f64); + match self.stepper.step(autorouter) { Ok(ControlFlow::Break(maybe_edit)) => Ok(ControlFlow::Break(maybe_edit)), Ok(ControlFlow::Continue(ReconfiguratorStatus::Running(status))) => { @@ -141,12 +150,24 @@ impl Abort> for MultilayerAutorouteReconfigurat } } -impl EstimateLinearProgress for MultilayerAutorouteReconfigurator { +impl EstimateProgress for MultilayerAutorouteReconfigurator { type Value = usize; type Subscale = LinearScale; - fn estimate_linear_progress(&self) -> LinearScale> { - self.stepper.estimate_linear_progress() + fn estimate_progress(&self) -> LinearScale> { + self.stepper.estimate_progress() + } +} + +impl GetMaybeReconfigurationTriggerProgress for MultilayerAutorouteReconfigurator { + type Subscale = (); + + fn reconfiguration_trigger_progress(&self) -> Option> { + Some(LinearScale::new( + (*self.reconfiguration_trigger.maybe_sma_rate_per_sec())?, + *self.reconfiguration_trigger.min_sma_rate_per_sec(), + (), + )) } } diff --git a/src/autorouter/planar_autoroute.rs b/src/autorouter/planar_autoroute.rs index 4e52c58..53a4d46 100644 --- a/src/autorouter/planar_autoroute.rs +++ b/src/autorouter/planar_autoroute.rs @@ -21,7 +21,7 @@ use crate::{ router::{ navcord::Navcord, navmesh::Navmesh, thetastar::ThetastarStepper, RouteStepper, Router, }, - stepper::{Abort, EstimateLinearProgress, LinearScale, Reconfigure, Step}, + stepper::{Abort, EstimateProgress, LinearScale, Reconfigure, Step}, }; use super::{ @@ -303,21 +303,21 @@ impl Reconfigure> for PlanarAutorouteExecutionS } } -impl EstimateLinearProgress for PlanarAutorouteExecutionStepper { +impl EstimateProgress for PlanarAutorouteExecutionStepper { type Value = usize; type Subscale = LinearScale; - fn estimate_linear_progress(&self) -> LinearScale> { + fn estimate_progress(&self) -> LinearScale> { LinearScale::new( self.curr_ratline_index, self.configuration().ratlines.len(), LinearScale::new( self.route .as_ref() - .map_or(0.0, |route| *route.estimate_linear_progress().value()), + .map_or(0.0, |route| *route.estimate_progress().value()), self.route .as_ref() - .map_or(0.0, |route| *route.estimate_linear_progress().maximum()), + .map_or(0.0, |route| *route.estimate_progress().reference()), (), ), ) diff --git a/src/autorouter/planar_reconfigurator.rs b/src/autorouter/planar_reconfigurator.rs index 3b305c4..a781dbf 100644 --- a/src/autorouter/planar_reconfigurator.rs +++ b/src/autorouter/planar_reconfigurator.rs @@ -24,9 +24,7 @@ use crate::{ drawing::graph::PrimitiveIndex, geometry::primitive::PrimitiveShape, router::{navcord::Navcord, navmesh::Navmesh, thetastar::ThetastarStepper}, - stepper::{ - Abort, EstimateLinearProgress, LinearScale, ReconfiguratorStatus, Reconfigure, Step, - }, + stepper::{Abort, EstimateProgress, LinearScale, ReconfiguratorStatus, Reconfigure, Step}, }; pub type PlanarAutorouteReconfiguratorStatus = @@ -120,12 +118,12 @@ impl Abort> for PlanarAutorouteReconfigurator { } } -impl EstimateLinearProgress for PlanarAutorouteReconfigurator { +impl EstimateProgress for PlanarAutorouteReconfigurator { type Value = usize; type Subscale = LinearScale; - fn estimate_linear_progress(&self) -> LinearScale> { - self.stepper.estimate_linear_progress() + fn estimate_progress(&self) -> LinearScale> { + self.stepper.estimate_progress() } } diff --git a/src/interactor/activity.rs b/src/interactor/activity.rs index ba13ad5..2f230dc 100644 --- a/src/interactor/activity.rs +++ b/src/interactor/activity.rs @@ -25,7 +25,9 @@ use crate::{ ng, thetastar::ThetastarStepper, }, - stepper::{Abort, EstimateLinearProgress, LinearScale, OnEvent, Step}, + stepper::{ + Abort, EstimateProgress, GetMaybeReconfigurationTriggerProgress, LinearScale, OnEvent, Step, + }, }; /// Stores the interactive input data from the user. @@ -99,17 +101,30 @@ impl Abort> for ActivityStepper { } // Since enum_dispatch does not really support generics, we implement this the -// long way. -impl EstimateLinearProgress for ActivityStepper { +// long way by using `match`. +impl EstimateProgress for ActivityStepper { type Value = usize; type Subscale = LinearScale; - fn estimate_linear_progress(&self) -> LinearScale> { + fn estimate_progress(&self) -> LinearScale> { match self { ActivityStepper::Interaction(..) => { LinearScale::new(0, 0, LinearScale::new(0.0, 0.0, ())) } - ActivityStepper::Execution(execution) => execution.estimate_linear_progress(), + ActivityStepper::Execution(execution) => execution.estimate_progress(), + } + } +} + +// Since enum_dispatch does not really support generics, we implement this the +// long way by using `match`. +impl GetMaybeReconfigurationTriggerProgress for ActivityStepper { + type Subscale = (); + + fn reconfiguration_trigger_progress(&self) -> Option> { + match self { + ActivityStepper::Interaction(..) => None, + ActivityStepper::Execution(execution) => execution.reconfiguration_trigger_progress(), } } } @@ -195,12 +210,20 @@ impl OnEvent, InteractiveEvent } } -impl EstimateLinearProgress for ActivityStepperWithStatus { +impl EstimateProgress for ActivityStepperWithStatus { type Value = usize; type Subscale = LinearScale; - fn estimate_linear_progress(&self) -> LinearScale> { - self.activity.estimate_linear_progress() + fn estimate_progress(&self) -> LinearScale> { + self.activity.estimate_progress() + } +} + +impl GetMaybeReconfigurationTriggerProgress for ActivityStepperWithStatus { + type Subscale = (); + + fn reconfiguration_trigger_progress(&self) -> Option> { + self.activity.reconfiguration_trigger_progress() } } diff --git a/src/router/ng/router.rs b/src/router/ng/router.rs index 683d40f..a9d0d5c 100644 --- a/src/router/ng/router.rs +++ b/src/router/ng/router.rs @@ -16,7 +16,7 @@ use crate::{ geometry::primitive::PrimitiveShape, graph::GenericIndex, layout::{poly::PolyWeight, Layout}, - stepper::{Abort, EstimateLinearProgress}, + stepper::{Abort, EstimateProgress}, }; use super::{ diff --git a/src/router/route.rs b/src/router/route.rs index 3d476a4..eb40590 100644 --- a/src/router/route.rs +++ b/src/router/route.rs @@ -19,7 +19,7 @@ use crate::{ thetastar::{ThetastarError, ThetastarStepper}, Router, RouterThetastarStrategy, }, - stepper::{EstimateLinearProgress, LinearScale, Step}, + stepper::{EstimateProgress, LinearScale, Step}, }; #[derive(Getters, Dissolve)] @@ -99,11 +99,11 @@ impl Step, BandTermsegIndex> for RouteStepper { } } -impl EstimateLinearProgress for RouteStepper { +impl EstimateProgress for RouteStepper { type Value = f64; type Subscale = (); - fn estimate_linear_progress(&self) -> LinearScale { - self.thetastar.estimate_linear_progress() + fn estimate_progress(&self) -> LinearScale { + self.thetastar.estimate_progress() } } diff --git a/src/router/thetastar.rs b/src/router/thetastar.rs index 08bf52c..3729fb5 100644 --- a/src/router/thetastar.rs +++ b/src/router/thetastar.rs @@ -20,7 +20,7 @@ use thiserror::Error; use std::cmp::Ordering; -use crate::stepper::{EstimateLinearProgress, LinearScale, Step}; +use crate::stepper::{EstimateProgress, LinearScale, Step}; #[derive(Copy, Clone, Debug)] pub struct MinScored(pub K, pub T); @@ -443,7 +443,7 @@ where } } -impl EstimateLinearProgress for ThetastarStepper +impl EstimateProgress for ThetastarStepper where G: GraphBase, G::NodeId: Eq + Ord, @@ -453,7 +453,7 @@ where type Value = K; type Subscale = (); - fn estimate_linear_progress(&self) -> LinearScale { + fn estimate_progress(&self) -> LinearScale { LinearScale::new( self.progress_estimate_value, self.progress_estimate_maximum, diff --git a/src/stepper.rs b/src/stepper.rs index d11801e..e5bc5cb 100644 --- a/src/stepper.rs +++ b/src/stepper.rs @@ -3,6 +3,7 @@ // SPDX-License-Identifier: MIT use core::ops::ControlFlow; +use std::{collections::VecDeque, time::Instant}; use derive_getters::Getters; @@ -86,34 +87,104 @@ pub trait OnEvent { #[derive(Clone, Copy, Debug, Getters)] pub struct LinearScale { value: V, - maximum: V, + reference: V, subscale: S, } impl LinearScale { - pub fn new(value: V, maximum: V, subscale: S) -> Self { + pub fn new(value: V, reference: V, subscale: S) -> Self { Self { value, - maximum, + reference, subscale, } } } -impl Default for LinearScale { - fn default() -> Self { - Self { - value: V::default(), - maximum: V::default(), - subscale: (), - } - } -} - /// Some steppers report estimates of how far they are from completion. -pub trait EstimateLinearProgress { +pub trait EstimateProgress { type Value; type Subscale; - fn estimate_linear_progress(&self) -> LinearScale; + fn estimate_progress(&self) -> LinearScale; +} + +pub trait GetMaybeReconfigurationTriggerProgress { + type Subscale; + + fn reconfiguration_trigger_progress(&self) -> Option>; +} + +#[derive(Clone, Debug, Getters)] +pub struct SmaRateReconfigurationTrigger { + #[getter(skip)] + sample_buffer: VecDeque, + #[getter(skip)] + last_instant: Instant, + #[getter(skip)] + last_value: f64, + maybe_sma_rate_per_sec: Option, + #[getter(skip)] + sample_buffer_size: usize, + #[getter(skip)] + sampling_interval_secs: f64, + min_sma_rate_per_sec: f64, +} + +impl SmaRateReconfigurationTrigger { + pub fn new( + sample_buffer_size: usize, + sampling_interval_secs: f64, + min_sma_rate_per_sec: f64, + ) -> Self { + Self { + sample_buffer: VecDeque::new(), + last_instant: Instant::now(), + last_value: 0.0, + maybe_sma_rate_per_sec: None, + sample_buffer_size, + sampling_interval_secs, + min_sma_rate_per_sec, + } + } + + pub fn update(&mut self, value: f64) -> bool { + let elapsed = self.last_instant.elapsed(); + let delta = value - self.last_value; + + if elapsed.as_secs_f64() >= self.sampling_interval_secs { + let count = (elapsed.as_secs_f64() / self.sampling_interval_secs) as usize; + let mut total_pushed = 0.0; + let mut total_popped = 0.0; + + for _ in 0..count { + let pushed = delta.max(0.0) / count as f64; + self.sample_buffer.push_back(delta.max(0.0) / count as f64); + total_pushed += pushed; + + if self.sample_buffer.len() > self.sample_buffer_size { + total_popped += self.sample_buffer.pop_front().unwrap_or_default(); + } + } + + if let Some(sma_rate_per_sec) = self.maybe_sma_rate_per_sec { + self.maybe_sma_rate_per_sec = Some( + sma_rate_per_sec + + (total_pushed - total_popped) / self.sample_buffer_size as f64, + ) + } else if self.sample_buffer.len() >= self.sample_buffer_size { + self.maybe_sma_rate_per_sec = + Some(self.sample_buffer.iter().sum::() / self.sample_buffer_size as f64) + } + + self.last_instant = Instant::now(); + self.last_value = value; + } + + if let Some(sma_rate_per_sec) = self.maybe_sma_rate_per_sec { + sma_rate_per_sec >= self.min_sma_rate_per_sec + } else { + true + } + } }