Skip to main content

runmat_analysis_fea/
progress.rs

1use std::cell::RefCell;
2use std::collections::BTreeMap;
3use std::sync::Arc;
4
5use serde::{Deserialize, Serialize};
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
8#[serde(rename_all = "snake_case")]
9pub enum FeaProgressPhase {
10    GeometryLoad,
11    RegionResolution,
12    MeshPrep,
13    ModelAssembly,
14    Solve,
15    Postprocess,
16    ArtifactPersistence,
17    Complete,
18}
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
21#[serde(rename_all = "snake_case")]
22pub enum FeaProgressStatus {
23    Started,
24    Advanced,
25    Completed,
26    Failed,
27    Cancelled,
28}
29
30#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
31#[serde(rename_all = "camelCase")]
32pub struct FeaProgressEvent {
33    pub operation: String,
34    pub phase: FeaProgressPhase,
35    pub status: FeaProgressStatus,
36    pub message: String,
37    #[serde(skip_serializing_if = "Option::is_none")]
38    pub current: Option<u64>,
39    #[serde(skip_serializing_if = "Option::is_none")]
40    pub total: Option<u64>,
41    #[serde(skip_serializing_if = "Option::is_none")]
42    pub fraction: Option<f64>,
43    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
44    pub metadata: BTreeMap<String, String>,
45}
46
47pub type FeaProgressHandler = Arc<dyn Fn(FeaProgressEvent) + Send + Sync + 'static>;
48pub type FeaCancellationPredicate = Arc<dyn Fn() -> bool + Send + Sync + 'static>;
49
50#[derive(Clone)]
51struct FeaProgressContext {
52    handler: Option<FeaProgressHandler>,
53    cancellation: Option<FeaCancellationPredicate>,
54}
55
56thread_local! {
57    static FEA_PROGRESS_CONTEXT: RefCell<Option<FeaProgressContext>> = const { RefCell::new(None) };
58}
59
60pub struct FeaProgressContextGuard {
61    previous: Option<FeaProgressContext>,
62}
63
64impl Drop for FeaProgressContextGuard {
65    fn drop(&mut self) {
66        FEA_PROGRESS_CONTEXT.with(|slot| {
67            slot.replace(self.previous.take());
68        });
69    }
70}
71
72pub fn replace_fea_progress_context(
73    handler: Option<FeaProgressHandler>,
74    cancellation: Option<FeaCancellationPredicate>,
75) -> FeaProgressContextGuard {
76    let previous = FEA_PROGRESS_CONTEXT.with(|slot| {
77        slot.replace(Some(FeaProgressContext {
78            handler,
79            cancellation,
80        }))
81    });
82    FeaProgressContextGuard { previous }
83}
84
85pub fn emit_progress(event: FeaProgressEvent) {
86    let handler = FEA_PROGRESS_CONTEXT.with(|slot| {
87        slot.borrow()
88            .as_ref()
89            .and_then(|context| context.handler.clone())
90    });
91    if let Some(handler) = handler {
92        handler(event);
93    }
94}
95
96pub fn emit_phase(
97    operation: impl Into<String>,
98    phase: FeaProgressPhase,
99    status: FeaProgressStatus,
100    message: impl Into<String>,
101    current: Option<u64>,
102    total: Option<u64>,
103) {
104    let fraction = match (current, total) {
105        (Some(current), Some(total)) if total > 0 => Some((current as f64 / total as f64).min(1.0)),
106        _ => None,
107    };
108    emit_progress(FeaProgressEvent {
109        operation: operation.into(),
110        phase,
111        status,
112        message: message.into(),
113        current,
114        total,
115        fraction,
116        metadata: BTreeMap::new(),
117    });
118}
119
120pub fn is_cancelled() -> bool {
121    FEA_PROGRESS_CONTEXT.with(|slot| {
122        slot.borrow()
123            .as_ref()
124            .and_then(|context| context.cancellation.clone())
125            .map(|is_cancelled| is_cancelled())
126            .unwrap_or(false)
127    })
128}
129
130pub fn check_cancelled(operation: impl Into<String>) -> Result<(), crate::contracts::FeaRunError> {
131    if is_cancelled() {
132        emit_phase(
133            operation,
134            FeaProgressPhase::Complete,
135            FeaProgressStatus::Cancelled,
136            "FEA solve cancelled",
137            None,
138            None,
139        );
140        return Err(crate::contracts::FeaRunError::Cancelled);
141    }
142    Ok(())
143}