use std::cell::Cell;
use std::rc::Rc;
use std::time::Duration;
use mlua::prelude::*;
use mlua::{UserData, UserDataMethods, UserDataRegistry};
use tokio::sync::Notify;
use super::{LOCAL_SCOPE, TASK_TOKEN};
pub(crate) struct CancelTokenInner {
cancelled: Cell<bool>,
notify: Notify,
}
#[derive(Clone)]
pub struct CancelToken(Rc<CancelTokenInner>);
impl Default for CancelToken {
fn default() -> Self {
Self::new()
}
}
impl CancelToken {
pub fn new() -> Self {
Self(Rc::new(CancelTokenInner {
cancelled: Cell::new(false),
notify: Notify::new(),
}))
}
pub fn cancel(&self) {
self.0.cancelled.set(true);
self.0.notify.notify_waiters();
}
pub fn is_cancelled(&self) -> bool {
self.0.cancelled.get()
}
pub async fn cancelled(&self) {
if self.is_cancelled() {
return;
}
let notified = self.0.notify.notified();
tokio::pin!(notified);
notified.as_mut().enable();
if self.is_cancelled() {
return;
}
notified.await;
}
}
impl UserData for CancelToken {
fn register(reg: &mut UserDataRegistry<Self>) {
reg.add_method("is_cancelled", |_, this, ()| Ok(this.is_cancelled()));
reg.add_method("cancel", |_, this, ()| {
this.cancel();
Ok(())
});
reg.add_method("check", |_, this, ()| {
if this.is_cancelled() {
Err(LuaError::external("task cancelled"))
} else {
Ok(())
}
});
}
}
pub fn effective_token() -> Option<CancelToken> {
if let Ok(t) = TASK_TOKEN.try_with(|t| t.clone()) {
return Some(t);
}
LOCAL_SCOPE.try_with(|s| s.borrow().token.clone()).ok()
}
pub(super) async fn race_sleep(dur: Duration) -> LuaResult<()> {
match effective_token() {
None => {
tokio::time::sleep(dur).await;
Ok(())
}
Some(t) => {
tokio::select! {
biased;
_ = t.cancelled() => {
Err(LuaError::external("task cancelled"))
}
_ = tokio::time::sleep(dur) => {
if t.is_cancelled() {
Err(LuaError::external("task cancelled"))
} else {
Ok(())
}
}
}
}
}
}
pub(super) async fn race_yield() -> LuaResult<()> {
tokio::task::yield_now().await;
if let Some(t) = effective_token() {
if t.is_cancelled() {
return Err(LuaError::external("task cancelled"));
}
}
Ok(())
}