Skip to content

Commit 2c03e3b

Browse files
committed
added fiting time evaluation
1 parent b55b23f commit 2c03e3b

6 files changed

Lines changed: 106 additions & 3 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "curve-fit"
3-
version = "0.1.7"
3+
version = "0.1.8"
44
edition = "2024"
55
authors = ["hexqnt <hexqntlab@gmail.com>"]
66
license = "Apache-2.0 OR MIT"

src/app.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -747,6 +747,8 @@ pub struct CurveFitApp {
747747
fit_metric_quantization: MetricQuantization,
748748
fit_preview_params: Option<CurveParams>,
749749
fit_preview_iteration: Option<u64>,
750+
fit_started_at: Option<Instant>,
751+
last_fit_duration: Option<Duration>,
750752
fit_result: Option<FitResult>,
751753
spline_result: Option<SplineResult>,
752754
active_fit_points: Option<Points>,
@@ -971,8 +973,36 @@ impl CurveFitApp {
971973
)
972974
}
973975

976+
// Таймер относится только к одному запуску фита:
977+
// новый старт сбрасывает прошлый замер, а при успехе фиксируется длительность.
978+
fn start_fit_timer(&mut self) {
979+
self.fit_started_at = Some(Instant::now());
980+
self.last_fit_duration = None;
981+
}
982+
983+
fn complete_fit_timer_successfully(&mut self) {
984+
self.last_fit_duration = self
985+
.fit_started_at
986+
.take()
987+
.map(|started_at| Instant::now().saturating_duration_since(started_at));
988+
}
989+
990+
fn reset_fit_timer(&mut self) {
991+
self.fit_started_at = None;
992+
self.last_fit_duration = None;
993+
}
994+
995+
fn format_fit_duration(duration: Duration) -> String {
996+
if duration < Duration::from_secs(1) {
997+
format!("{} ms", duration.as_millis())
998+
} else {
999+
format!("{:.2} s", duration.as_secs_f64())
1000+
}
1001+
}
1002+
9741003
fn clear_fit_outputs(&mut self) {
9751004
self.cancel_fit_and_discard_updates();
1005+
self.reset_fit_timer();
9761006
self.fit_result = None;
9771007
self.spline_result = None;
9781008
self.active_fit_points = None;
@@ -1333,6 +1363,8 @@ impl Default for CurveFitApp {
13331363
fit_metric_quantization: MetricQuantization::Disabled,
13341364
fit_preview_params: None,
13351365
fit_preview_iteration: None,
1366+
fit_started_at: None,
1367+
last_fit_duration: None,
13361368
fit_result: None,
13371369
spline_result: None,
13381370
active_fit_points: None,

src/app/fit_worker.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ impl CurveFitApp {
136136
}
137137
Ok(FitWorkerMessage::Stopped) => {
138138
self.fit_in_progress = false;
139+
self.reset_fit_timer();
139140
self.active_fit_points = None;
140141
self.finalize_replay_after_fit_stopped();
141142
if !self.discard_fit_worker_updates {
@@ -170,8 +171,10 @@ impl CurveFitApp {
170171
);
171172
self.finalize_replay_after_fit_completion();
172173
self.fit_result = Some(result);
174+
self.complete_fit_timer_successfully();
173175
self.status = Some(StatusMessage::FitCompleted);
174176
} else {
177+
self.reset_fit_timer();
175178
self.set_fit_stopped_status_if_fitting();
176179
}
177180
keep_receiver = false;
@@ -192,8 +195,10 @@ impl CurveFitApp {
192195
self.upsert_spline_replay_frame(result.iterations, spline_plot_curve);
193196
self.finalize_replay_after_fit_completion();
194197
self.spline_result = Some(result);
198+
self.complete_fit_timer_successfully();
195199
self.status = Some(StatusMessage::FitCompleted);
196200
} else {
201+
self.reset_fit_timer();
197202
self.set_fit_stopped_status_if_fitting();
198203
}
199204
self.active_fit_points = None;
@@ -202,6 +207,7 @@ impl CurveFitApp {
202207
}
203208
Ok(FitWorkerMessage::Failed(error)) => {
204209
self.fit_in_progress = false;
210+
self.reset_fit_timer();
205211
self.active_fit_points = None;
206212
if !self.discard_fit_worker_updates {
207213
self.status = Some(StatusMessage::Error(error));
@@ -214,6 +220,7 @@ impl CurveFitApp {
214220
Err(TryRecvError::Empty) => break,
215221
Err(TryRecvError::Disconnected) => {
216222
self.fit_in_progress = false;
223+
self.reset_fit_timer();
217224
self.active_fit_points = None;
218225
if !self.discard_fit_worker_updates {
219226
self.status = Some(StatusMessage::Error(
@@ -315,6 +322,7 @@ impl CurveFitApp {
315322
Ok(params) => params,
316323
Err(error) => {
317324
self.fit_in_progress = false;
325+
self.reset_fit_timer();
318326
self.status = Some(StatusMessage::Error(error));
319327
self.active_fit_points = None;
320328
break;
@@ -345,6 +353,7 @@ impl CurveFitApp {
345353
result.params = match normalization.denormalize_params(&result.params) {
346354
Ok(params) => params,
347355
Err(error) => {
356+
self.reset_fit_timer();
348357
self.status = Some(StatusMessage::Error(error));
349358
self.active_fit_points = None;
350359
break;
@@ -376,18 +385,21 @@ impl CurveFitApp {
376385
self.upsert_parametric_replay_frame(result.iterations, result.params.clone());
377386
self.finalize_replay_after_fit_completion();
378387
self.fit_result = Some(result);
388+
self.complete_fit_timer_successfully();
379389
self.status = Some(StatusMessage::FitCompleted);
380390
break;
381391
}
382392
Ok(IncrementalFitStep::Cancelled) => {
383393
self.fit_in_progress = false;
394+
self.reset_fit_timer();
384395
self.finalize_replay_after_fit_stopped();
385396
self.status = Some(StatusMessage::FitStopped);
386397
self.active_fit_points = None;
387398
break;
388399
}
389400
Err(error) => {
390401
self.fit_in_progress = false;
402+
self.reset_fit_timer();
391403
self.status = Some(StatusMessage::Error(error.to_string()));
392404
self.active_fit_points = None;
393405
break;
@@ -423,19 +435,22 @@ impl CurveFitApp {
423435
self.upsert_spline_replay_frame(result.iterations, spline_plot_curve);
424436
self.finalize_replay_after_fit_completion();
425437
self.spline_result = Some(result);
438+
self.complete_fit_timer_successfully();
426439
self.status = Some(StatusMessage::FitCompleted);
427440
self.active_fit_points = None;
428441
break;
429442
}
430443
Ok(IncrementalSplineFitStep::Cancelled) => {
431444
self.fit_in_progress = false;
445+
self.reset_fit_timer();
432446
self.finalize_replay_after_fit_stopped();
433447
self.status = Some(StatusMessage::FitStopped);
434448
self.active_fit_points = None;
435449
break;
436450
}
437451
Err(error) => {
438452
self.fit_in_progress = false;
453+
self.reset_fit_timer();
439454
self.status = Some(StatusMessage::Error(error.to_string()));
440455
self.active_fit_points = None;
441456
break;
@@ -671,6 +686,7 @@ impl CurveFitApp {
671686
}
672687
};
673688
self.upsert_spline_replay_frame(0, Self::plot_points_from_pairs(initial_curve));
689+
self.start_fit_timer();
674690
self.status = Some(StatusMessage::FittingInProgress);
675691

676692
#[cfg(not(target_arch = "wasm32"))]
@@ -703,6 +719,7 @@ impl CurveFitApp {
703719
self.fit_in_progress = true;
704720
}
705721
Err(error) => {
722+
self.reset_fit_timer();
706723
self.status = Some(StatusMessage::Error(error.to_string()));
707724
}
708725
}
@@ -769,6 +786,7 @@ impl CurveFitApp {
769786
metric_quantization,
770787
);
771788
self.upsert_parametric_replay_frame(0, initial_params.clone());
789+
self.start_fit_timer();
772790
self.status = Some(StatusMessage::FittingInProgress);
773791

774792
#[cfg(not(target_arch = "wasm32"))]
@@ -805,6 +823,7 @@ impl CurveFitApp {
805823
self.fit_in_progress = true;
806824
}
807825
Err(error) => {
826+
self.reset_fit_timer();
808827
self.active_fit_points = None;
809828
self.status = Some(StatusMessage::Error(error.to_string()));
810829
}

src/app/tests.rs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1077,6 +1077,20 @@ fn successful_fit_starts_replay_from_first_frame() {
10771077
}
10781078
}
10791079

1080+
#[cfg(not(target_arch = "wasm32"))]
1081+
#[test]
1082+
fn successful_fit_records_duration() {
1083+
let mut app = make_linear_fit_app();
1084+
1085+
app.run_fit();
1086+
assert!(app.fit_in_progress);
1087+
wait_fit_completion(&mut app);
1088+
1089+
assert!(matches!(app.status, Some(StatusMessage::FitCompleted)));
1090+
assert!(app.last_fit_duration.is_some());
1091+
assert!(app.fit_started_at.is_none());
1092+
}
1093+
10801094
#[cfg(not(target_arch = "wasm32"))]
10811095
#[test]
10821096
fn successful_fit_with_auto_replay_disabled_selects_last_iteration() {
@@ -1261,6 +1275,7 @@ fn points_edit_parse_error_status_restores_completed_when_fixed() {
12611275
rmse: 0.0,
12621276
iterations: 1,
12631277
}),
1278+
last_fit_duration: Some(std::time::Duration::from_millis(184)),
12641279
status: Some(StatusMessage::FitCompleted),
12651280
..Default::default()
12661281
};
@@ -1277,6 +1292,34 @@ fn points_edit_parse_error_status_restores_completed_when_fixed() {
12771292
app.invalidate_points_cache();
12781293
app.refresh_status_after_points_edit();
12791294
assert!(matches!(app.status, Some(StatusMessage::FitCompleted)));
1295+
assert_eq!(
1296+
app.last_fit_duration,
1297+
Some(std::time::Duration::from_millis(184))
1298+
);
1299+
}
1300+
1301+
#[test]
1302+
fn clear_fit_outputs_resets_fit_duration_state() {
1303+
let mut app = CurveFitApp::default();
1304+
app.start_fit_timer();
1305+
app.last_fit_duration = Some(std::time::Duration::from_millis(42));
1306+
1307+
app.clear_fit_outputs();
1308+
1309+
assert!(app.fit_started_at.is_none());
1310+
assert!(app.last_fit_duration.is_none());
1311+
}
1312+
1313+
#[test]
1314+
fn format_fit_duration_uses_expected_units_at_boundary() {
1315+
assert_eq!(
1316+
CurveFitApp::format_fit_duration(std::time::Duration::from_millis(999)),
1317+
"999 ms"
1318+
);
1319+
assert_eq!(
1320+
CurveFitApp::format_fit_duration(std::time::Duration::from_millis(1_000)),
1321+
"1.00 s"
1322+
);
12801323
}
12811324

12821325
#[test]

src/app/ui/status_panel.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use super::*;
2+
use std::borrow::Cow;
23

34
const COLLAPSED_METRIC_SELECTOR_WIDTH: f32 = 150.0;
45

@@ -106,7 +107,15 @@ pub(super) fn ui_status(app: &CurveFitApp, ui: &mut egui::Ui) {
106107
};
107108
ui.horizontal(|ui| {
108109
ui.colored_label(color, "●");
109-
ui.label(status.text(app.ui_language));
110+
let status_text = match (status, app.last_fit_duration) {
111+
(StatusMessage::FitCompleted, Some(duration)) => Cow::Owned(format!(
112+
"{} ({})",
113+
status.text(app.ui_language),
114+
CurveFitApp::format_fit_duration(duration)
115+
)),
116+
_ => Cow::Borrowed(status.text(app.ui_language)),
117+
};
118+
ui.label(status_text.as_ref());
110119
});
111120
}
112121
}

0 commit comments

Comments
 (0)