use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex, RwLock};
use std::time::Duration;
use tokio_util::sync::CancellationToken;
use crate::util::debug_log::DebugLog;
#[derive(Debug)]
struct ScheduledFunctionState {
delay: RwLock<Option<Duration>>,
running: AtomicBool,
cancel: Mutex<Option<CancellationToken>>,
}
pub(crate) struct ScheduledFunction {
func: Arc<dyn Fn() + Send + Sync>,
state: Arc<ScheduledFunctionState>,
}
impl std::fmt::Debug for ScheduledFunction {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter
.debug_struct("ScheduledFunction")
.field("delay", &self.delay())
.field("running", &self.state.running.load(Ordering::SeqCst))
.finish()
}
}
impl ScheduledFunction {
pub(crate) fn new(func: impl Fn() + Send + Sync + 'static) -> ScheduledFunction {
ScheduledFunction::with_delay(func, None)
}
pub(crate) fn with_delay(
func: impl Fn() + Send + Sync + 'static,
delay: Option<Duration>,
) -> ScheduledFunction {
ScheduledFunction {
func: Arc::new(func),
state: Arc::new(ScheduledFunctionState {
delay: RwLock::new(delay),
running: AtomicBool::new(false),
cancel: Mutex::new(None),
}),
}
}
pub(crate) fn delay(&self) -> Option<Duration> {
*self.state.delay.read().unwrap_or_else(|error| error.into_inner())
}
pub(crate) fn set_delay(&self, delay: Option<Duration>) {
*self.state.delay.write().unwrap_or_else(|error| error.into_inner()) = delay;
}
pub(crate) fn start(&self) {
if self.state.running.swap(true, Ordering::SeqCst) {
return;
}
let Ok(handle) = tokio::runtime::Handle::try_current() else {
self.state.running.store(false, Ordering::SeqCst);
DebugLog::write_line(
"ScheduledFunction",
"Unable to start a scheduled function outside of a tokio runtime.",
);
return;
};
let token = CancellationToken::new();
{
let mut cancel = self.state.cancel.lock().unwrap_or_else(|error| error.into_inner());
*cancel = Some(token.clone());
}
let delay = self.delay();
let func = self.func.clone();
handle.spawn(async move {
match delay {
Some(period) if !period.is_zero() => {
let mut interval = tokio::time::interval(period);
interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay);
loop {
tokio::select! {
_ = token.cancelled() => break,
_ = interval.tick() => func(),
}
}
}
_ => {
tokio::select! {
biased;
_ = token.cancelled() => {}
_ = std::future::ready(()) => func(),
}
}
}
});
}
pub(crate) fn stop(&self) {
if !self.state.running.swap(false, Ordering::SeqCst) {
return;
}
let token = {
let mut cancel = self.state.cancel.lock().unwrap_or_else(|error| error.into_inner());
cancel.take()
};
if let Some(token) = token {
token.cancel();
}
}
}
impl Drop for ScheduledFunction {
fn drop(&mut self) {
self.stop();
}
}