use core::sync::atomic::{AtomicBool, AtomicU32, Ordering};
use super::error::StegoError;
static STEP: AtomicU32 = AtomicU32::new(0);
static TOTAL: AtomicU32 = AtomicU32::new(0);
static CANCELLED: AtomicBool = AtomicBool::new(false);
pub fn init(total: u32) {
CANCELLED.store(false, Ordering::Relaxed);
STEP.store(0, Ordering::Relaxed);
TOTAL.store(total, Ordering::Relaxed);
notify();
}
pub fn set_total(total: u32) {
TOTAL.store(total, Ordering::Relaxed);
notify();
}
pub fn cancel() {
CANCELLED.store(true, Ordering::Relaxed);
}
pub fn is_cancelled() -> bool {
CANCELLED.load(Ordering::Relaxed)
}
pub fn check_cancelled() -> Result<(), StegoError> {
if is_cancelled() {
Err(StegoError::Cancelled)
} else {
Ok(())
}
}
pub fn advance() {
let total = TOTAL.load(Ordering::Relaxed);
if total == 0 {
STEP.fetch_add(1, Ordering::Relaxed);
} else {
let _ = STEP.fetch_update(Ordering::Relaxed, Ordering::Relaxed, |s| {
if s + 1 < total { Some(s + 1) } else { Some(s) }
});
}
notify();
}
pub fn get() -> (u32, u32) {
(STEP.load(Ordering::Relaxed), TOTAL.load(Ordering::Relaxed))
}
pub fn advance_by(n: u32) {
for _ in 0..n {
advance();
}
}
pub fn finish() {
let t = TOTAL.load(Ordering::Relaxed);
STEP.store(t, Ordering::Relaxed);
notify();
}
#[cfg(feature = "wasm")]
mod wasm_cb {
use std::cell::RefCell;
thread_local! {
static CALLBACK: RefCell<Option<js_sys::Function>> = RefCell::new(None);
}
pub fn set(cb: Option<js_sys::Function>) {
CALLBACK.with(|c: &RefCell<Option<js_sys::Function>>| *c.borrow_mut() = cb);
}
pub fn notify(step: u32, total: u32) {
CALLBACK.with(|c: &RefCell<Option<js_sys::Function>>| {
if let Some(ref f) = *c.borrow() {
let _ = f.call2(
&wasm_bindgen::JsValue::NULL,
&wasm_bindgen::JsValue::from(step),
&wasm_bindgen::JsValue::from(total),
);
}
});
}
}
#[cfg(feature = "wasm")]
pub fn set_wasm_callback(cb: Option<js_sys::Function>) {
wasm_cb::set(cb);
}
#[cfg(feature = "wasm")]
fn notify() {
let (s, t) = get();
wasm_cb::notify(s, t);
}
#[cfg(not(feature = "wasm"))]
fn notify() {
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn cancel_flag_propagates() {
init(10);
assert!(!is_cancelled());
assert!(check_cancelled().is_ok());
cancel();
assert!(is_cancelled());
let err = check_cancelled().unwrap_err();
assert!(
matches!(err, StegoError::Cancelled),
"expected Cancelled, got {err:?}"
);
init(5);
assert!(!is_cancelled());
assert!(check_cancelled().is_ok());
}
}