From 18e8f9812c48d31ca2578f98600e32e91234c139 Mon Sep 17 00:00:00 2001 From: Mikolaj Wielgus Date: Tue, 4 Nov 2025 16:37:41 +0100 Subject: [PATCH] feat(stepper): Replace overengineered SMA rate timeout with two accumulators --- crates/topola-egui/src/status_bar.rs | 12 +-- src/autorouter/execution.rs | 10 +-- src/autorouter/multilayer_reconfigurator.rs | 21 +++-- src/autorouter/planar_autoroute.rs | 2 +- src/autorouter/planar_reconfigurator.rs | 12 +-- src/interactor/activity.rs | 16 ++-- src/stepper.rs | 85 +++++---------------- 7 files changed, 52 insertions(+), 106 deletions(-) diff --git a/crates/topola-egui/src/status_bar.rs b/crates/topola-egui/src/status_bar.rs index deb1780..504668c 100644 --- a/crates/topola-egui/src/status_bar.rs +++ b/crates/topola-egui/src/status_bar.rs @@ -6,7 +6,7 @@ use std::ops::ControlFlow; use topola::{ interactor::activity::ActivityStepperWithStatus, - stepper::{EstimateProgress, GetMaybeReconfigurationTriggerProgress}, + stepper::{EstimateProgress, GetTimeoutProgress}, }; use crate::{translator::Translator, viewport::Viewport}; @@ -45,17 +45,17 @@ impl StatusBar { if let Some(activity) = maybe_activity { let progress = activity.estimate_progress(); let value = progress.value(); - let maximum = progress.reference(); + let maximum = progress.maximum(); let ratio = *value as f32 / *maximum as f32; - if let Some(trigger_progress) = activity.reconfiguration_trigger_progress() { + if let Some(trigger_progress) = activity.timeout_progress() { ui.add(egui::ProgressBar::new(ratio).text(format!( - "{:.1}% ({:.1}/{:.1}) (sma: {:.1}, min: {:.1}))", + "{:.1}% ({:.1}/{:.1}) (timeout: {:.1}/{:.1}))", ratio * 100.0, value, maximum, trigger_progress.value(), - trigger_progress.reference(), + trigger_progress.maximum(), ))); } else { ui.add(egui::ProgressBar::new(ratio).text(format!( @@ -68,7 +68,7 @@ impl StatusBar { let linear_subprogress = progress.subscale(); let value = linear_subprogress.value(); - let maximum = linear_subprogress.reference(); + let maximum = linear_subprogress.maximum(); let ratio = *value as f32 / *maximum as f32; ui.add(egui::ProgressBar::new(ratio).text(format!( diff --git a/src/autorouter/execution.rs b/src/autorouter/execution.rs index ce455e3..a8b3e02 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, EstimateProgress, GetMaybeReconfigurationTriggerProgress, LinearScale, Step}, + stepper::{Abort, EstimateProgress, GetTimeoutProgress, LinearScale, Step}, }; use super::{ @@ -199,14 +199,12 @@ impl EstimateProgress for ExecutionStepper { // Since enum_dispatch does not really support generics, we implement this the // long way by using `match`. -impl GetMaybeReconfigurationTriggerProgress for ExecutionStepper { +impl GetTimeoutProgress for ExecutionStepper { type Subscale = (); - fn reconfiguration_trigger_progress(&self) -> Option> { + fn timeout_progress(&self) -> Option> { match self { - ExecutionStepper::MultilayerAutoroute(autoroute) => { - autoroute.reconfiguration_trigger_progress() - } + ExecutionStepper::MultilayerAutoroute(autoroute) => autoroute.timeout_progress(), ExecutionStepper::PlanarAutoroute(autoroute) => None, ExecutionStepper::TopoAutoroute(..) => None, ExecutionStepper::PlaceVia(..) => None, diff --git a/src/autorouter/multilayer_reconfigurator.rs b/src/autorouter/multilayer_reconfigurator.rs index a79a00e..5e0324a 100644 --- a/src/autorouter/multilayer_reconfigurator.rs +++ b/src/autorouter/multilayer_reconfigurator.rs @@ -20,7 +20,6 @@ use crate::{ IncrementFailedRatlineLayersMultilayerAutorouteReconfigurer, MakeNextMultilayerAutorouteConfiguration, MultilayerAutorouteReconfigurer, }, - planar_autoroute::PlanarAutorouteConfigurationStatus, planar_preconfigurer::PlanarAutoroutePreconfigurerInput, planar_reconfigurator::PlanarAutorouteReconfiguratorStatus, Autorouter, AutorouterError, @@ -30,8 +29,8 @@ use crate::{ geometry::primitive::PrimitiveShape, router::{navcord::Navcord, navmesh::Navmesh, thetastar::ThetastarStepper}, stepper::{ - Abort, EstimateProgress, GetMaybeReconfigurationTriggerProgress, LinearScale, - ReconfiguratorStatus, Reconfigure, SmaRateReconfigurationTrigger, Step, + Abort, EstimateProgress, GetTimeoutProgress, LinearScale, ReconfiguratorStatus, + Reconfigure, Step, TimeVsProgressAccumulatorTimeout, }, }; @@ -40,7 +39,7 @@ pub type MultilayerReconfiguratorStatus = pub struct MultilayerAutorouteReconfigurator { stepper: MultilayerAutorouteExecutionStepper, - reconfiguration_trigger: SmaRateReconfigurationTrigger, + timeout: TimeVsProgressAccumulatorTimeout, reconfigurer: MultilayerAutorouteReconfigurer, options: MultilayerAutorouteOptions, } @@ -74,7 +73,7 @@ impl MultilayerAutorouteReconfigurator { preconfiguration, options, )?, - reconfiguration_trigger: SmaRateReconfigurationTrigger::new(20, 0.5, 0.1), + timeout: TimeVsProgressAccumulatorTimeout::new(10.0, 5.0), reconfigurer, options, }) @@ -86,7 +85,7 @@ impl MultilayerAutorouteReconfigurator { ) -> Result, MultilayerReconfiguratorStatus>, AutorouterError> { // Reset the reconfiguration trigger. - self.reconfiguration_trigger = SmaRateReconfigurationTrigger::new(20, 1.0, 0.1); + self.timeout = TimeVsProgressAccumulatorTimeout::new(10.0, 5.0); loop { let Some(configuration) = self.reconfigurer.next_configuration(autorouter) else { @@ -117,7 +116,7 @@ impl Step, Option, MultilayerReconfi ) -> Result, MultilayerReconfiguratorStatus>, AutorouterError> { if !self - .reconfiguration_trigger + .timeout .update(*self.estimate_progress().value() as f64) { return self.reconfigure(autorouter); @@ -161,13 +160,13 @@ impl EstimateProgress for MultilayerAutorouteReconfigurator { } } -impl GetMaybeReconfigurationTriggerProgress for MultilayerAutorouteReconfigurator { +impl GetTimeoutProgress for MultilayerAutorouteReconfigurator { type Subscale = (); - fn reconfiguration_trigger_progress(&self) -> Option> { + fn timeout_progress(&self) -> Option> { Some(LinearScale::new( - (*self.reconfiguration_trigger.maybe_sma_rate_per_sec())?, - *self.reconfiguration_trigger.min_sma_rate_per_sec(), + self.timeout.start_instant().elapsed().as_secs_f64(), + *self.timeout.progress_accumulator(), (), )) } diff --git a/src/autorouter/planar_autoroute.rs b/src/autorouter/planar_autoroute.rs index 53a4d46..d12fd61 100644 --- a/src/autorouter/planar_autoroute.rs +++ b/src/autorouter/planar_autoroute.rs @@ -317,7 +317,7 @@ impl EstimateProgress for PlanarAutorouteExecutionStepper { .map_or(0.0, |route| *route.estimate_progress().value()), self.route .as_ref() - .map_or(0.0, |route| *route.estimate_progress().reference()), + .map_or(0.0, |route| *route.estimate_progress().maximum()), (), ), ) diff --git a/src/autorouter/planar_reconfigurator.rs b/src/autorouter/planar_reconfigurator.rs index 60358c0..88360d8 100644 --- a/src/autorouter/planar_reconfigurator.rs +++ b/src/autorouter/planar_reconfigurator.rs @@ -25,8 +25,8 @@ use crate::{ geometry::primitive::PrimitiveShape, router::{navcord::Navcord, navmesh::Navmesh, thetastar::ThetastarStepper}, stepper::{ - Abort, EstimateProgress, LinearScale, ReconfiguratorStatus, Reconfigure, - SmaRateReconfigurationTrigger, Step, + Abort, EstimateProgress, LinearScale, ReconfiguratorStatus, Reconfigure, Step, + TimeVsProgressAccumulatorTimeout, }, }; @@ -35,7 +35,7 @@ pub type PlanarAutorouteReconfiguratorStatus = pub struct PlanarAutorouteReconfigurator { stepper: PlanarAutorouteExecutionStepper, - reconfiguration_trigger: SmaRateReconfigurationTrigger, + timeout: TimeVsProgressAccumulatorTimeout, reconfigurer: PlanarAutorouteReconfigurer, options: PlanarAutorouteOptions, } @@ -65,7 +65,7 @@ impl PlanarAutorouteReconfigurator { Ok(Self { stepper: PlanarAutorouteExecutionStepper::new(autorouter, preconfiguration, options)?, - reconfiguration_trigger: SmaRateReconfigurationTrigger::new(5, 0.5, 0.1), + timeout: TimeVsProgressAccumulatorTimeout::new(3.0, 1.0), // Note: I assume here that the first permutation is the same as the original order. reconfigurer, options, @@ -77,7 +77,7 @@ impl PlanarAutorouteReconfigurator { autorouter: &mut Autorouter, ) -> Result, PlanarAutorouteReconfiguratorStatus>, AutorouterError> { - self.reconfiguration_trigger = SmaRateReconfigurationTrigger::new(5, 0.5, 0.1); + self.timeout = TimeVsProgressAccumulatorTimeout::new(3.0, 1.0); loop { let Some(configuration) = self @@ -111,7 +111,7 @@ impl Step, Option, PlanarAutorouteRe ) -> Result, PlanarAutorouteReconfiguratorStatus>, AutorouterError> { if !self - .reconfiguration_trigger + .timeout .update(*self.estimate_progress().value() as f64) { return self.reconfigure(autorouter); diff --git a/src/interactor/activity.rs b/src/interactor/activity.rs index 2f230dc..3525181 100644 --- a/src/interactor/activity.rs +++ b/src/interactor/activity.rs @@ -25,9 +25,7 @@ use crate::{ ng, thetastar::ThetastarStepper, }, - stepper::{ - Abort, EstimateProgress, GetMaybeReconfigurationTriggerProgress, LinearScale, OnEvent, Step, - }, + stepper::{Abort, EstimateProgress, GetTimeoutProgress, LinearScale, OnEvent, Step}, }; /// Stores the interactive input data from the user. @@ -118,13 +116,13 @@ impl EstimateProgress for ActivityStepper { // Since enum_dispatch does not really support generics, we implement this the // long way by using `match`. -impl GetMaybeReconfigurationTriggerProgress for ActivityStepper { +impl GetTimeoutProgress for ActivityStepper { type Subscale = (); - fn reconfiguration_trigger_progress(&self) -> Option> { + fn timeout_progress(&self) -> Option> { match self { ActivityStepper::Interaction(..) => None, - ActivityStepper::Execution(execution) => execution.reconfiguration_trigger_progress(), + ActivityStepper::Execution(execution) => execution.timeout_progress(), } } } @@ -219,11 +217,11 @@ impl EstimateProgress for ActivityStepperWithStatus { } } -impl GetMaybeReconfigurationTriggerProgress for ActivityStepperWithStatus { +impl GetTimeoutProgress for ActivityStepperWithStatus { type Subscale = (); - fn reconfiguration_trigger_progress(&self) -> Option> { - self.activity.reconfiguration_trigger_progress() + fn timeout_progress(&self) -> Option> { + self.activity.timeout_progress() } } diff --git a/src/stepper.rs b/src/stepper.rs index c61fae5..6e0d3e3 100644 --- a/src/stepper.rs +++ b/src/stepper.rs @@ -3,7 +3,7 @@ // SPDX-License-Identifier: MIT use core::ops::ControlFlow; -use std::{collections::VecDeque, time::Instant}; +use std::time::Instant; use derive_getters::Getters; @@ -87,7 +87,7 @@ pub trait OnEvent { #[derive(Clone, Copy, Debug, Getters)] pub struct LinearScale { value: V, - reference: V, + maximum: V, subscale: S, } @@ -95,7 +95,7 @@ impl LinearScale { pub fn new(value: V, reference: V, subscale: S) -> Self { Self { value, - reference, + maximum: reference, subscale, } } @@ -109,87 +109,38 @@ pub trait EstimateProgress { fn estimate_progress(&self) -> LinearScale; } -pub trait GetMaybeReconfigurationTriggerProgress { +pub trait GetTimeoutProgress { type Subscale; - fn reconfiguration_trigger_progress(&self) -> Option>; + fn timeout_progress(&self) -> Option>; } #[derive(Clone, Debug, Getters)] -pub struct SmaRateReconfigurationTrigger { - #[getter(skip)] - sample_buffer: VecDeque, - #[getter(skip)] - last_instant: Instant, +pub struct TimeVsProgressAccumulatorTimeout { + start_instant: Instant, #[getter(skip)] last_max_value: f64, + progress_accumulator: f64, #[getter(skip)] - incoming_max_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, + progress_bonus_s: f64, } -impl SmaRateReconfigurationTrigger { - pub fn new( - sample_buffer_size: usize, - sampling_interval_secs: f64, - min_sma_rate_per_sec: f64, - ) -> Self { +impl TimeVsProgressAccumulatorTimeout { + pub fn new(initial_timeout_s: f64, progress_bonus_s: f64) -> Self { Self { - sample_buffer: VecDeque::new(), - last_instant: Instant::now(), + start_instant: Instant::now(), last_max_value: 0.0, - incoming_max_value: 0.0, - maybe_sma_rate_per_sec: None, - sample_buffer_size, - sampling_interval_secs, - min_sma_rate_per_sec, + progress_accumulator: initial_timeout_s, + progress_bonus_s, } } pub fn update(&mut self, value: f64) -> bool { - self.incoming_max_value = self.incoming_max_value.max(value); - - let elapsed = self.last_instant.elapsed(); - - if elapsed.as_secs_f64() >= self.sampling_interval_secs { - let delta = self.incoming_max_value - self.last_max_value; - 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 / count as f64) / self.sampling_interval_secs; - self.sample_buffer.push_back(delta / 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_max_value = self.incoming_max_value; + if value > self.last_max_value { + self.progress_accumulator += (value - self.last_max_value) * self.progress_bonus_s; + self.last_max_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 - } + self.start_instant.elapsed().as_secs_f64() < self.progress_accumulator } }