use std::sync::{
Arc, Mutex, MutexGuard, OnceLock, PoisonError,
atomic::{AtomicUsize, Ordering},
};
#[derive(Clone, Debug, PartialEq)]
pub struct BackgroundWork {
pub title: String,
pub progress: Option<f32>,
}
pub trait BackgroundActivity: Send + Sync {
fn show(&self, work: Option<&BackgroundWork>);
}
pub type BackgroundActivityRef = Arc<dyn BackgroundActivity>;
fn slot() -> &'static Mutex<Option<BackgroundActivityRef>> {
static SLOT: OnceLock<Mutex<Option<BackgroundActivityRef>>> = OnceLock::new();
SLOT.get_or_init(|| Mutex::new(None))
}
pub fn set_platform_background_activity(activity: BackgroundActivityRef) {
if let Some(work) = leases().shown() {
activity.show(Some(&work));
}
if let Ok(mut s) = slot().lock() {
*s = Some(activity);
}
}
pub fn clear_platform_background_activity() {
if let Ok(mut s) = slot().lock() {
*s = None;
}
}
pub fn background_activity() -> Option<BackgroundActivityRef> {
slot().lock().ok().and_then(|s| s.clone())
}
#[derive(Default)]
struct Leases {
next: u64,
active: Vec<(u64, BackgroundWork)>,
}
impl Leases {
fn shown(&self) -> Option<BackgroundWork> {
self.active.last().map(|(_, work)| work.clone())
}
}
static ACTIVE_LEASES: AtomicUsize = AtomicUsize::new(0);
fn leases() -> MutexGuard<'static, Leases> {
static LEASES: OnceLock<Mutex<Leases>> = OnceLock::new();
LEASES
.get_or_init(Mutex::default)
.lock()
.unwrap_or_else(PoisonError::into_inner)
}
fn update(change: impl FnOnce(&mut Leases)) {
let (before, after) = {
let mut leases = leases();
let before = leases.shown().map(|work| Shown::of(&work));
change(&mut leases);
ACTIVE_LEASES.store(leases.active.len(), Ordering::Release);
(before, leases.shown())
};
if before != after.as_ref().map(Shown::of)
&& let Some(activity) = background_activity()
{
activity.show(after.as_ref());
}
}
#[derive(PartialEq)]
struct Shown {
title: String,
percent: Option<u8>,
}
impl Shown {
fn of(work: &BackgroundWork) -> Self {
Self {
title: work.title.clone(),
percent: work
.progress
.map(|fraction| (fraction.clamp(0.0, 1.0) * 100.0) as u8),
}
}
}
pub struct BackgroundWorkLease {
id: u64,
}
impl BackgroundWorkLease {
pub fn set_progress(&self, progress: Option<f32>) {
self.change(|work| work.progress = progress);
}
pub fn set_title(&self, title: impl Into<String>) {
let title = title.into();
self.change(|work| work.title = title);
}
fn change(&self, change: impl FnOnce(&mut BackgroundWork)) {
update(|leases| {
if let Some((_, work)) = leases.active.iter_mut().find(|(id, _)| *id == self.id) {
change(work);
}
});
}
}
impl Drop for BackgroundWorkLease {
fn drop(&mut self) {
update(|leases| leases.active.retain(|(id, _)| *id != self.id));
}
}
pub fn acquire_background_work(title: impl Into<String>) -> BackgroundWorkLease {
let title = title.into();
let mut id = 0;
update(|leases| {
id = leases.next;
leases.next += 1;
leases.active.push((
id,
BackgroundWork {
title,
progress: None,
},
));
});
BackgroundWorkLease { id }
}
pub fn background_active() -> bool {
ACTIVE_LEASES.load(Ordering::Acquire) != 0
}
#[cfg(test)]
#[path = "tests/background_tests.rs"]
mod tests;