use std::cell::Cell;
use std::future::Future;
use std::rc::Rc;
use async_lock::Mutex;
use perspective_js::utils::ApiResult;
use super::pubsub::PubSub;
pub struct RenderGuard {
_private: (),
}
#[derive(Default)]
struct SlotState {
id: Cell<u64>,
last: Cell<u64>,
parked: Cell<bool>,
settled_id: Cell<u64>,
on_settle: PubSub<()>,
}
#[derive(Default)]
struct DebounceMutexData {
held: Cell<bool>,
mutex: Mutex<()>,
default_slot: Rc<SlotState>,
}
struct HeldFlag<'a>(&'a Cell<bool>);
impl<'a> HeldFlag<'a> {
fn set(cell: &'a Cell<bool>) -> Self {
cell.set(true);
Self(cell)
}
}
impl Drop for HeldFlag<'_> {
fn drop(&mut self) {
self.0.set(false);
}
}
struct SettleGuard<'a> {
state: &'a SlotState,
next: u64,
}
impl<'a> SettleGuard<'a> {
fn park(state: &'a SlotState, next: u64) -> Self {
state.parked.set(true);
Self { state, next }
}
}
impl Drop for SettleGuard<'_> {
fn drop(&mut self) {
self.state.parked.set(false);
if self.state.settled_id.get() < self.next {
self.state.settled_id.set(self.next);
}
self.state.on_settle.emit(());
}
}
#[derive(Clone, Default)]
pub struct DebounceMutex(Rc<DebounceMutexData>);
impl DebounceMutex {
pub fn is_held(&self) -> bool {
self.0.held.get()
}
pub async fn lock<T>(&self, f: impl Future<Output = T>) -> T {
self.lock_with(|_| f).await
}
pub async fn lock_with<T, F, Fut>(&self, f: F) -> T
where
F: FnOnce(RenderGuard) -> Fut,
Fut: Future<Output = T>,
{
let guard = self.0.mutex.lock().await;
let held = HeldFlag::set(&self.0.held);
let result = f(RenderGuard { _private: () }).await;
drop(held);
drop(guard);
result
}
pub async fn debounce(&self, f: impl Future<Output = ApiResult<()>>) -> ApiResult<()> {
self.debounce_with(|_| f).await
}
pub async fn debounce_with<T, F, Fut>(&self, f: F) -> ApiResult<T>
where
T: Default,
F: FnOnce(RenderGuard) -> Fut,
Fut: Future<Output = ApiResult<T>>,
{
DebounceSlot {
mutex: self.clone(),
state: self.0.default_slot.clone(),
}
.debounce_with(f)
.await
}
pub fn slot(&self) -> DebounceSlot {
DebounceSlot {
mutex: self.clone(),
state: Default::default(),
}
}
}
#[derive(Clone)]
pub struct DebounceSlot {
mutex: DebounceMutex,
state: Rc<SlotState>,
}
impl DebounceSlot {
pub async fn debounce_with<T, F, Fut>(&self, f: F) -> ApiResult<T>
where
T: Default,
F: FnOnce(RenderGuard) -> Fut,
Fut: Future<Output = ApiResult<T>>,
{
let state = &self.state;
let next = state.id.get() + 1;
if state.parked.get() {
self.await_settled(next).await;
return Ok(T::default());
}
let settle = SettleGuard::park(state, next);
let guard = self.mutex.0.mutex.lock().await;
state.parked.set(false);
let result = if state.last.get() < next {
let next = state.id.get() + 1;
state.id.set(next);
let held = HeldFlag::set(&self.mutex.0.held);
let result = f(RenderGuard { _private: () }).await;
drop(held);
if result.is_ok() {
state.last.set(next);
}
result
} else {
Ok(T::default())
};
drop(guard);
drop(settle);
result
}
async fn await_settled(&self, next: u64) {
while self.state.settled_id.get() < next {
if self.state.on_settle.read_next().await.is_err() {
break;
}
}
}
}