#[cfg(test)]
mod test;
use std::sync::Arc;
use std::sync::Condvar;
use std::sync::Mutex;
use std::sync::TryLockError;
use std::sync::atomic;
use std::sync::atomic::AtomicBool;
#[derive(Debug)]
pub struct BackgroundRunner<T> {
sync: Arc<Sync<T>>,
background_thread: Option<std::thread::JoinHandle<()>>,
}
impl<T: 'static + Send> BackgroundRunner<T> {
pub fn new(task: impl 'static + Send + FnMut(&T)) -> Self
where
T: Default,
{
Self::with_init_data(T::default(), task)
}
pub fn with_init_data(init_data: T, mut task: impl 'static + Send + FnMut(&T)) -> Self {
let sync = Arc::new(Sync::new(init_data));
let cloned_sync = Arc::clone(&sync);
let background_thread = Some(std::thread::spawn(move || {
loop {
let sync_status = cloned_sync.wait_read(|data| task(data));
if sync_status == SyncStatus::Disconnected {
break;
}
}
}));
Self {
sync,
background_thread,
}
}
pub fn update(&self, new_data: &T)
where
T: Clone,
{
self.update_with(|data| data.clone_from(new_data));
}
pub fn update_with(&self, f: impl FnOnce(&mut T)) {
self.sync.try_write(f);
}
pub fn wait_and_update(&self, new_data: &T)
where
T: Clone,
{
self.wait_and_update_with(|data| data.clone_from(new_data));
}
pub fn wait_and_update_with(&self, f: impl FnOnce(&mut T)) {
self.sync.wait_write(f);
}
pub fn join(mut self) {
self.sync.disconnect();
if let Some(thread) = self.background_thread.take() {
let _ = thread.join();
};
}
}
impl<T> Drop for BackgroundRunner<T> {
fn drop(&mut self) {
self.sync.disconnect();
}
}
#[must_use]
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
enum SyncStatus {
Connected,
Disconnected,
}
#[derive(Debug)]
struct Sync<T> {
mutex: Mutex<State<T>>,
cvar: Condvar,
is_disconnected: AtomicBool,
}
impl<T> Sync<T> {
fn new(value: T) -> Self {
let state = State::new(value);
let mutex = Mutex::new(state);
let cvar = Condvar::new();
let is_disconnected = AtomicBool::new(false);
Self {
mutex,
cvar,
is_disconnected,
}
}
fn wait_read(&self, f: impl FnOnce(&T)) -> SyncStatus {
let guard = self.mutex.lock().unwrap();
let mut state = self
.cvar
.wait_while(guard, |state| {
!state.is_dirty && !self.is_disconnected.load(atomic::Ordering::SeqCst)
})
.unwrap();
if state.is_dirty {
f(state.read());
}
if self.is_disconnected.load(atomic::Ordering::SeqCst) {
return SyncStatus::Disconnected;
}
SyncStatus::Connected
}
fn try_write(&self, f: impl FnOnce(&mut T)) {
let mut state = match self.mutex.try_lock() {
Ok(state) => state,
Err(TryLockError::Poisoned(p)) => panic!("Runner panicked: {p}"),
Err(TryLockError::WouldBlock) => return,
};
f(state.write());
self.cvar.notify_all();
}
fn wait_write(&self, f: impl FnOnce(&mut T)) {
let mut state = self.mutex.lock().expect("Runner panicked");
f(state.write());
self.cvar.notify_all();
}
fn disconnect(&self) {
self.is_disconnected.store(true, atomic::Ordering::SeqCst);
self.cvar.notify_all();
}
}
#[derive(Debug)]
struct State<T> {
data: T,
is_dirty: bool,
}
impl<T> State<T> {
fn new(data: T) -> Self {
Self {
data,
is_dirty: false,
}
}
fn read(&mut self) -> &T {
self.is_dirty = false;
&self.data
}
fn write(&mut self) -> &mut T {
self.is_dirty = true;
&mut self.data
}
}