1use std::sync::Arc;
33use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
34use std::time::{Duration, Instant};
35
36#[derive(Debug, Clone)]
38pub struct Progress {
39 pub current: u64,
41
42 pub total: u64,
44
45 pub message: String,
47
48 pub elapsed: Duration,
50
51 pub estimated_remaining: Option<Duration>,
53}
54
55impl Progress {
56 pub fn new(current: u64, total: u64, message: impl Into<String>) -> Self {
58 Self {
59 current,
60 total,
61 message: message.into(),
62 elapsed: Duration::ZERO,
63 estimated_remaining: None,
64 }
65 }
66
67 #[inline]
69 pub fn fraction(&self) -> f64 {
70 if self.total == 0 {
71 0.0
72 } else {
73 (self.current as f64) / (self.total as f64)
74 }
75 }
76
77 #[inline]
79 pub fn percent(&self) -> u32 {
80 (self.fraction() * 100.0).round() as u32
81 }
82
83 #[inline]
85 pub fn is_complete(&self) -> bool {
86 self.current >= self.total
87 }
88}
89
90pub type ProgressCallback = Box<dyn Fn(&Progress) -> bool + Send + Sync>;
94
95#[derive(Debug)]
100pub struct ProgressTracker {
101 current: AtomicU64,
102 total: u64,
103 cancelled: AtomicBool,
104 start_time: Instant,
105 last_callback_time: std::sync::Mutex<Instant>,
106 callback_interval: Duration,
107}
108
109impl ProgressTracker {
110 pub fn new(total: u64) -> Self {
112 Self {
113 current: AtomicU64::new(0),
114 total,
115 cancelled: AtomicBool::new(false),
116 start_time: Instant::now(),
117 last_callback_time: std::sync::Mutex::new(Instant::now()),
118 callback_interval: Duration::from_millis(100), }
120 }
121
122 pub fn with_interval(total: u64, interval: Duration) -> Self {
124 let mut tracker = Self::new(total);
125 tracker.callback_interval = interval;
126 tracker
127 }
128
129 #[inline]
131 pub fn increment(&self) {
132 self.current.fetch_add(1, Ordering::Relaxed);
133 }
134
135 #[inline]
137 pub fn increment_by(&self, amount: u64) {
138 self.current.fetch_add(amount, Ordering::Relaxed);
139 }
140
141 #[inline]
143 pub fn set(&self, value: u64) {
144 self.current.store(value, Ordering::Relaxed);
145 }
146
147 #[inline]
149 pub fn current(&self) -> u64 {
150 self.current.load(Ordering::Relaxed)
151 }
152
153 #[inline]
155 pub fn total(&self) -> u64 {
156 self.total
157 }
158
159 #[inline]
161 pub fn is_cancelled(&self) -> bool {
162 self.cancelled.load(Ordering::Relaxed)
163 }
164
165 pub fn cancel(&self) {
167 self.cancelled.store(true, Ordering::Relaxed);
168 }
169
170 #[inline]
172 pub fn fraction(&self) -> f64 {
173 if self.total == 0 {
174 0.0
175 } else {
176 (self.current() as f64) / (self.total as f64)
177 }
178 }
179
180 #[inline]
182 pub fn elapsed(&self) -> Duration {
183 self.start_time.elapsed()
184 }
185
186 pub fn estimated_remaining(&self) -> Option<Duration> {
188 let current = self.current();
189 if current == 0 {
190 return None;
191 }
192
193 let elapsed = self.elapsed();
194 let rate = current as f64 / elapsed.as_secs_f64();
195
196 if rate > 0.0 {
197 let remaining = (self.total - current) as f64 / rate;
198 Some(Duration::from_secs_f64(remaining))
199 } else {
200 None
201 }
202 }
203
204 pub fn snapshot(&self, message: impl Into<String>) -> Progress {
206 Progress {
207 current: self.current(),
208 total: self.total,
209 message: message.into(),
210 elapsed: self.elapsed(),
211 estimated_remaining: self.estimated_remaining(),
212 }
213 }
214
215 pub fn maybe_callback(
219 &self,
220 callback: Option<&ProgressCallback>,
221 message: impl Into<String>,
222 ) -> bool {
223 if self.is_cancelled() {
224 return false;
225 }
226
227 let callback = match callback {
228 Some(cb) => cb,
229 None => return true,
230 };
231
232 let now = Instant::now();
234 {
235 let last = self.last_callback_time.lock().unwrap();
236 if now.duration_since(*last) < self.callback_interval {
237 return true;
238 }
239 }
240
241 {
243 let mut last = self.last_callback_time.lock().unwrap();
244 *last = now;
245 }
246
247 let progress = self.snapshot(message);
248 let should_continue = callback(&progress);
249
250 if !should_continue {
251 self.cancel();
252 }
253
254 should_continue
255 }
256}
257
258pub type SharedProgressTracker = Arc<ProgressTracker>;
260
261pub fn shared_tracker(total: u64) -> SharedProgressTracker {
263 Arc::new(ProgressTracker::new(total))
264}
265
266#[derive(Debug, Clone, Copy, PartialEq, Eq)]
272pub enum OperationType {
273 Validate,
275 Repair,
277 FillHoles,
279 FixWinding,
281 Remesh,
283 Decimate,
285 Subdivide,
287 ThicknessAnalysis,
289 SelfIntersection,
291 Boolean,
293 Morph,
295 Registration,
297 SurfaceReconstruction,
299 LatticeGeneration,
301 Slice,
303}
304
305#[derive(Debug, Clone)]
307pub struct OperationEstimate {
308 pub estimated_seconds: f64,
310
311 pub confidence: f64,
313
314 pub estimated_memory_bytes: u64,
316
317 pub supports_progress: bool,
319
320 pub supports_cancellation: bool,
322
323 pub estimated_iterations: Option<u64>,
325
326 pub complexity: String,
328}
329
330impl OperationEstimate {
331 fn new(seconds: f64, complexity: &str) -> Self {
332 Self {
333 estimated_seconds: seconds,
334 confidence: 0.7,
335 estimated_memory_bytes: 0,
336 supports_progress: true,
337 supports_cancellation: true,
338 estimated_iterations: None,
339 complexity: complexity.to_string(),
340 }
341 }
342}
343
344pub fn estimate_operation_time(
362 vertex_count: usize,
363 face_count: usize,
364 operation: OperationType,
365) -> OperationEstimate {
366 let v = vertex_count as f64;
367 let f = face_count as f64;
368
369 match operation {
370 OperationType::Validate => {
371 let seconds = (v + f) / 10_000_000.0;
373 OperationEstimate::new(seconds, "O(V + F)")
374 }
375
376 OperationType::Repair => {
377 let seconds = (v + f) / 1_000_000.0;
379 OperationEstimate::new(seconds, "O(V + F) multiple passes")
380 }
381
382 OperationType::FillHoles => {
383 let seconds = v.sqrt() / 10_000.0;
385 OperationEstimate::new(seconds, "O(boundary edges)")
386 }
387
388 OperationType::FixWinding => {
389 let seconds = f / 5_000_000.0;
391 OperationEstimate::new(seconds, "O(F)")
392 }
393
394 OperationType::Remesh => {
395 let iterations = 7.0;
397 let seconds = v * iterations / 500_000.0;
398 let mut est = OperationEstimate::new(seconds, "O(V × iterations)");
399 est.estimated_iterations = Some(iterations as u64);
400 est
401 }
402
403 OperationType::Decimate => {
404 let seconds = v * v.ln() / 1_000_000.0;
406 OperationEstimate::new(seconds, "O(V log V)")
407 }
408
409 OperationType::Subdivide => {
410 let seconds = f / 2_000_000.0;
412 OperationEstimate::new(seconds, "O(F × 4^iterations)")
413 }
414
415 OperationType::ThicknessAnalysis => {
416 let seconds = v * f.ln() / 500_000.0;
418 OperationEstimate::new(seconds, "O(V log F) with BVH")
419 }
420
421 OperationType::SelfIntersection => {
422 let seconds = f * f.ln() / 100_000.0;
424 OperationEstimate::new(seconds, "O(F log F) with BVH")
425 }
426
427 OperationType::Boolean => {
428 let seconds = f * f.ln() / 50_000.0;
430 OperationEstimate::new(seconds, "O(F log F)")
431 }
432
433 OperationType::Morph => {
434 let seconds = v * v.sqrt() / 100_000.0;
436 OperationEstimate::new(seconds, "O(V√V) local RBF")
437 }
438
439 OperationType::Registration => {
440 let iterations = 50.0;
442 let seconds = v * iterations / 1_000_000.0;
443 let mut est = OperationEstimate::new(seconds, "O(V × iterations)");
444 est.estimated_iterations = Some(iterations as u64);
445 est
446 }
447
448 OperationType::SurfaceReconstruction => {
449 let grid_size = (v / 10.0).powf(1.0 / 3.0).max(10.0);
451 let seconds = (v + grid_size.powi(3)) / 500_000.0;
452 OperationEstimate::new(seconds, "O(V + grid³)")
453 }
454
455 OperationType::LatticeGeneration => {
456 let seconds = v / 100_000.0;
458 OperationEstimate::new(seconds, "O(volume / cell_size³)")
459 }
460
461 OperationType::Slice => {
462 let layers = 100.0;
464 let seconds = f * layers / 10_000_000.0;
465 let mut est = OperationEstimate::new(seconds, "O(F × layers)");
466 est.estimated_iterations = Some(layers as u64);
467 est
468 }
469 }
470}
471
472pub trait ProgressReporter {
478 fn report_progress(&self, current: u64, total: u64, message: &str) -> bool;
480}
481
482pub struct NoOpProgressReporter;
484
485impl ProgressReporter for NoOpProgressReporter {
486 #[inline]
487 fn report_progress(&self, _current: u64, _total: u64, _message: &str) -> bool {
488 true
489 }
490}
491
492pub struct CallbackProgressReporter<'a> {
494 callback: &'a ProgressCallback,
495 start_time: Instant,
496}
497
498impl<'a> CallbackProgressReporter<'a> {
499 pub fn new(callback: &'a ProgressCallback) -> Self {
501 Self {
502 callback,
503 start_time: Instant::now(),
504 }
505 }
506}
507
508impl ProgressReporter for CallbackProgressReporter<'_> {
509 fn report_progress(&self, current: u64, total: u64, message: &str) -> bool {
510 let elapsed = self.start_time.elapsed();
511 let estimated_remaining = if current > 0 {
512 let rate = current as f64 / elapsed.as_secs_f64();
513 if rate > 0.0 {
514 let remaining = (total - current) as f64 / rate;
515 Some(Duration::from_secs_f64(remaining))
516 } else {
517 None
518 }
519 } else {
520 None
521 };
522
523 let progress = Progress {
524 current,
525 total,
526 message: message.to_string(),
527 elapsed,
528 estimated_remaining,
529 };
530
531 (self.callback)(&progress)
532 }
533}
534
535#[cfg(test)]
540mod tests {
541 use super::*;
542 use std::sync::atomic::AtomicU32;
543
544 #[test]
545 fn test_progress_fraction() {
546 let p = Progress::new(50, 100, "test");
547 assert!((p.fraction() - 0.5).abs() < 1e-10);
548 assert_eq!(p.percent(), 50);
549 }
550
551 #[test]
552 fn test_progress_complete() {
553 let p1 = Progress::new(50, 100, "incomplete");
554 assert!(!p1.is_complete());
555
556 let p2 = Progress::new(100, 100, "complete");
557 assert!(p2.is_complete());
558 }
559
560 #[test]
561 fn test_progress_zero_total() {
562 let p = Progress::new(0, 0, "empty");
563 assert!((p.fraction() - 0.0).abs() < 1e-10);
564 assert_eq!(p.percent(), 0);
565 }
566
567 #[test]
568 fn test_progress_tracker() {
569 let tracker = ProgressTracker::new(100);
570
571 assert_eq!(tracker.current(), 0);
572 assert_eq!(tracker.total(), 100);
573 assert!(!tracker.is_cancelled());
574
575 tracker.increment();
576 assert_eq!(tracker.current(), 1);
577
578 tracker.increment_by(9);
579 assert_eq!(tracker.current(), 10);
580
581 tracker.set(50);
582 assert_eq!(tracker.current(), 50);
583 assert!((tracker.fraction() - 0.5).abs() < 1e-10);
584 }
585
586 #[test]
587 fn test_progress_tracker_cancel() {
588 let tracker = ProgressTracker::new(100);
589
590 assert!(!tracker.is_cancelled());
591 tracker.cancel();
592 assert!(tracker.is_cancelled());
593 }
594
595 #[test]
596 fn test_shared_tracker() {
597 let tracker = shared_tracker(100);
598 let tracker_clone = tracker.clone();
599
600 tracker.increment();
602 assert_eq!(tracker_clone.current(), 1);
603 }
604
605 #[test]
606 fn test_progress_callback() {
607 let counter = Arc::new(AtomicU32::new(0));
608 let counter_clone = counter.clone();
609
610 let callback: ProgressCallback = Box::new(move |p| {
611 counter_clone.fetch_add(1, Ordering::SeqCst);
612 p.current < 5 });
614
615 let tracker = ProgressTracker::with_interval(10, Duration::ZERO);
616
617 for i in 0..10 {
618 tracker.set(i);
619 if !tracker.maybe_callback(Some(&callback), "test") {
620 break;
621 }
622 }
623
624 let calls = counter.load(Ordering::SeqCst);
626 assert!(calls >= 5, "Expected at least 5 calls, got {}", calls);
627 }
628
629 #[test]
630 fn test_operation_estimate() {
631 let est = estimate_operation_time(10000, 20000, OperationType::Validate);
632
633 assert!(est.estimated_seconds > 0.0);
634 assert!(est.confidence > 0.0 && est.confidence <= 1.0);
635 assert!(est.supports_progress);
636 }
637
638 #[test]
639 fn test_operation_estimates_scale() {
640 let small = estimate_operation_time(1000, 2000, OperationType::Remesh);
642 let large = estimate_operation_time(100000, 200000, OperationType::Remesh);
643
644 assert!(
645 large.estimated_seconds > small.estimated_seconds,
646 "Large mesh estimate ({}) should be greater than small ({})",
647 large.estimated_seconds,
648 small.estimated_seconds
649 );
650 }
651
652 #[test]
653 fn test_noop_progress_reporter() {
654 let reporter = NoOpProgressReporter;
655 assert!(reporter.report_progress(50, 100, "test"));
656 }
657
658 #[test]
659 fn test_callback_progress_reporter() {
660 let called = Arc::new(AtomicBool::new(false));
661 let called_clone = called.clone();
662
663 let callback: ProgressCallback = Box::new(move |_p| {
664 called_clone.store(true, Ordering::SeqCst);
665 true
666 });
667
668 let reporter = CallbackProgressReporter::new(&callback);
669 let result = reporter.report_progress(50, 100, "test");
670
671 assert!(result);
672 assert!(called.load(Ordering::SeqCst));
673 }
674}