phasm_core/stego/
progress.rs1use core::sync::atomic::{AtomicBool, AtomicU32, Ordering};
13
14use super::error::StegoError;
15
16static STEP: AtomicU32 = AtomicU32::new(0);
17static TOTAL: AtomicU32 = AtomicU32::new(0);
18static CANCELLED: AtomicBool = AtomicBool::new(false);
19
20pub fn init(total: u32) {
23 CANCELLED.store(false, Ordering::Relaxed);
24 STEP.store(0, Ordering::Relaxed);
25 TOTAL.store(total, Ordering::Relaxed);
26 notify();
27}
28
29pub fn set_total(total: u32) {
33 TOTAL.store(total, Ordering::Relaxed);
34 notify();
35}
36
37pub fn cancel() {
42 CANCELLED.store(true, Ordering::Relaxed);
43}
44
45pub fn is_cancelled() -> bool {
47 CANCELLED.load(Ordering::Relaxed)
48}
49
50pub fn check_cancelled() -> Result<(), StegoError> {
55 if is_cancelled() {
56 Err(StegoError::Cancelled)
57 } else {
58 Ok(())
59 }
60}
61
62pub fn advance() {
67 let total = TOTAL.load(Ordering::Relaxed);
68 if total == 0 {
69 STEP.fetch_add(1, Ordering::Relaxed);
71 } else {
72 let _ = STEP.fetch_update(Ordering::Relaxed, Ordering::Relaxed, |s| {
74 if s + 1 < total { Some(s + 1) } else { Some(s) }
75 });
76 }
77 notify();
78}
79
80pub fn get() -> (u32, u32) {
82 (STEP.load(Ordering::Relaxed), TOTAL.load(Ordering::Relaxed))
83}
84
85pub fn advance_by(n: u32) {
88 for _ in 0..n {
89 advance();
90 }
91}
92
93pub fn finish() {
95 let t = TOTAL.load(Ordering::Relaxed);
96 STEP.store(t, Ordering::Relaxed);
97 notify();
98}
99
100#[cfg(feature = "wasm")]
105mod wasm_cb {
106 use std::cell::RefCell;
107
108 thread_local! {
109 static CALLBACK: RefCell<Option<js_sys::Function>> = RefCell::new(None);
110 }
111
112 pub fn set(cb: Option<js_sys::Function>) {
113 CALLBACK.with(|c: &RefCell<Option<js_sys::Function>>| *c.borrow_mut() = cb);
114 }
115
116 pub fn notify(step: u32, total: u32) {
117 CALLBACK.with(|c: &RefCell<Option<js_sys::Function>>| {
118 if let Some(ref f) = *c.borrow() {
119 let _ = f.call2(
120 &wasm_bindgen::JsValue::NULL,
121 &wasm_bindgen::JsValue::from(step),
122 &wasm_bindgen::JsValue::from(total),
123 );
124 }
125 });
126 }
127}
128
129#[cfg(feature = "wasm")]
131pub fn set_wasm_callback(cb: Option<js_sys::Function>) {
132 wasm_cb::set(cb);
133}
134
135#[cfg(feature = "wasm")]
136fn notify() {
137 let (s, t) = get();
138 wasm_cb::notify(s, t);
139}
140
141#[cfg(not(feature = "wasm"))]
142fn notify() {
143 }
145
146#[cfg(test)]
147mod tests {
148 use super::*;
149
150 #[test]
151 fn cancel_flag_propagates() {
152 init(10);
154 assert!(!is_cancelled());
155 assert!(check_cancelled().is_ok());
156
157 cancel();
159 assert!(is_cancelled());
160
161 let err = check_cancelled().unwrap_err();
163 assert!(
164 matches!(err, StegoError::Cancelled),
165 "expected Cancelled, got {err:?}"
166 );
167
168 init(5);
170 assert!(!is_cancelled());
171 assert!(check_cancelled().is_ok());
172 }
173}