use std::{
borrow::BorrowMut,
sync::{Arc, Condvar, Mutex, mpsc},
time::Duration,
};
#[derive(Clone)]
#[non_exhaustive]
#[doc(hidden)]
pub enum ChangedEvent {
LibAboutToReload(BlockReload),
LibReloaded,
}
impl std::fmt::Debug for ChangedEvent {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::LibAboutToReload(_) => write!(f, "LibAboutToReload"),
Self::LibReloaded => write!(f, "LibReloaded"),
}
}
}
#[derive(Debug)]
pub struct BlockReload {
pub(crate) pending: Arc<(Mutex<usize>, Condvar)>,
}
impl Clone for BlockReload {
fn clone(&self) -> Self {
**(self.pending.0.lock().unwrap().borrow_mut()) += 1;
Self {
pending: self.pending.clone(),
}
}
}
impl Drop for BlockReload {
fn drop(&mut self) {
let (counter, cond) = &*self.pending;
*counter.lock().unwrap() -= 1;
cond.notify_one();
}
}
pub struct LibReloadObserver {
#[doc(hidden)]
pub rx: mpsc::Receiver<ChangedEvent>,
}
impl LibReloadObserver {
pub fn wait_for_about_to_reload(&self) -> BlockReload {
loop {
match self.rx.recv() {
Ok(ChangedEvent::LibAboutToReload(block)) => return block,
Err(err) => {
panic!("LibReloadObserver failed to wait for event from reloader: {err}")
}
_ => continue,
}
}
}
pub fn wait_for_about_to_reload_timeout(&self, timeout: Duration) -> Option<BlockReload> {
loop {
match self.rx.recv_timeout(timeout) {
Ok(ChangedEvent::LibAboutToReload(block)) => return Some(block),
Err(_) => return None,
_ => continue,
}
}
}
pub fn wait_for_reload(&self) {
loop {
match self.rx.recv() {
Ok(ChangedEvent::LibReloaded) => return,
Err(err) => {
panic!("LibReloadObserver failed to wait for event from reloader: {err}")
}
_ => continue,
}
}
}
pub fn wait_for_reload_timeout(&self, timeout: Duration) -> bool {
loop {
match self.rx.recv_timeout(timeout) {
Ok(ChangedEvent::LibReloaded) => return true,
Err(_) => return false,
_ => continue,
}
}
}
}
#[derive(Default)]
#[doc(hidden)]
pub struct LibReloadNotifier {
subscribers: Arc<Mutex<Vec<mpsc::Sender<ChangedEvent>>>>,
}
impl LibReloadNotifier {
#[doc(hidden)]
pub fn send_about_to_reload_event_and_wait_for_blocks(&self) {
let pending = Arc::new((Mutex::new(1), std::sync::Condvar::new()));
let block = BlockReload {
pending: pending.clone(),
};
self.notify(ChangedEvent::LibAboutToReload(block));
let (counter, cond) = &*pending;
log::trace!(
"about-to-change library event, waiting for {}",
counter.lock().unwrap()
);
let _guard = cond
.wait_while(counter.lock().unwrap(), |pending| {
log::trace!(
"about-to-change library event, now waiting for {}",
*pending
);
*pending > 0
})
.unwrap();
}
#[doc(hidden)]
pub fn send_reloaded_event(&self) {
self.notify(ChangedEvent::LibReloaded);
}
fn notify(&self, evt: ChangedEvent) {
if let Ok(mut subscribers) = self.subscribers.try_lock() {
let n = subscribers.len();
log::trace!("sending {evt:?} to {n} subscribers");
subscribers.retain(|tx| tx.send(evt.clone()).is_ok());
let removed = n - subscribers.len();
if removed > 0 {
log::debug!(
"removing {removed} subscriber{}",
if removed == 1 { "" } else { "s" }
);
}
}
}
#[doc(hidden)]
pub fn subscribe(&mut self) -> LibReloadObserver {
log::trace!("subscribe to lib change");
let (tx, rx) = mpsc::channel();
let mut subscribers = self.subscribers.lock().unwrap();
subscribers.push(tx);
LibReloadObserver { rx }
}
}