use std::future::Future;
use std::time::Duration;
#[cfg(not(target_arch = "wasm32"))]
pub fn spawn<F>(future: F) -> TaskHandle
where
F: Future<Output = ()> + Send + 'static,
{
TaskHandle {
inner: tokio::spawn(future),
}
}
#[cfg(target_arch = "wasm32")]
pub fn spawn<F>(future: F) -> TaskHandle
where
F: Future<Output = ()> + 'static,
{
wasm_bindgen_futures::spawn_local(future);
TaskHandle
}
#[cfg(not(target_arch = "wasm32"))]
pub struct TaskHandle {
inner: tokio::task::JoinHandle<()>,
}
#[cfg(target_arch = "wasm32")]
pub struct TaskHandle;
impl TaskHandle {
pub fn abort(&self) {
#[cfg(not(target_arch = "wasm32"))]
{
self.inner.abort();
}
}
pub async fn join_within(&mut self, timeout: Duration) -> bool {
#[cfg(not(target_arch = "wasm32"))]
{
tokio::select! {
_ = &mut self.inner => true,
_ = tokio::time::sleep(timeout) => false,
}
}
#[cfg(target_arch = "wasm32")]
{
let _ = timeout;
true
}
}
}
#[cfg(not(target_arch = "wasm32"))]
pub async fn spawn_blocking<F, T>(f: F) -> Result<T, crate::error::LiteError>
where
F: FnOnce() -> T + Send + 'static,
T: Send + 'static,
{
tokio::task::spawn_blocking(f)
.await
.map_err(|e| crate::error::LiteError::JoinError {
detail: e.to_string(),
})
}
#[cfg(target_arch = "wasm32")]
pub async fn spawn_blocking<F, T>(f: F) -> Result<T, crate::error::LiteError>
where
F: FnOnce() -> T,
{
Ok(f())
}
#[cfg(not(target_arch = "wasm32"))]
pub async fn sleep(duration: Duration) {
tokio::time::sleep(duration).await;
}
#[cfg(target_arch = "wasm32")]
pub async fn sleep(duration: Duration) {
gloo_timers::future::sleep(duration).await;
}
pub struct Interval {
#[cfg(not(target_arch = "wasm32"))]
inner: tokio::time::Interval,
#[cfg(target_arch = "wasm32")]
period: Duration,
}
impl Interval {
pub fn delay_missed_ticks(&mut self) {
#[cfg(not(target_arch = "wasm32"))]
{
self.inner
.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay);
}
}
pub async fn tick(&mut self) {
#[cfg(not(target_arch = "wasm32"))]
{
self.inner.tick().await;
}
#[cfg(target_arch = "wasm32")]
{
gloo_timers::future::sleep(self.period).await;
}
}
}
pub fn interval(period: Duration) -> Interval {
#[cfg(not(target_arch = "wasm32"))]
{
Interval {
inner: tokio::time::interval(period),
}
}
#[cfg(target_arch = "wasm32")]
{
Interval { period }
}
}
pub fn now_millis() -> u64 {
#[cfg(not(target_arch = "wasm32"))]
{
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_millis() as u64
}
#[cfg(target_arch = "wasm32")]
{
js_sys::Date::now() as u64
}
}
pub fn now_millis_i64() -> i64 {
now_millis() as i64
}
static LAST_SYS_MS: std::sync::atomic::AtomicI64 = std::sync::atomic::AtomicI64::new(0);
pub fn monotonic_millis_i64() -> i64 {
use std::sync::atomic::Ordering;
let now = now_millis_i64();
loop {
let last = LAST_SYS_MS.load(Ordering::Relaxed);
let next = if now > last { now } else { last + 1 };
match LAST_SYS_MS.compare_exchange_weak(last, next, Ordering::Relaxed, Ordering::Relaxed) {
Ok(_) => return next,
Err(_) => continue,
}
}
}
pub fn now_secs() -> u64 {
now_millis() / 1000
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn spawn_blocking_works() {
let result = spawn_blocking(|| 42).await.unwrap();
assert_eq!(result, 42);
}
#[tokio::test]
async fn spawn_blocking_string() {
let result = spawn_blocking(|| "hello".to_string()).await.unwrap();
assert_eq!(result, "hello");
}
#[tokio::test]
async fn sleep_returns() {
sleep(Duration::from_millis(1)).await;
}
#[test]
fn now_millis_nonzero() {
let ts = now_millis();
assert!(ts > 0, "timestamp should be nonzero on native");
}
#[test]
fn now_secs_reasonable() {
let ts = now_secs();
assert!(ts > 1_704_067_200, "timestamp {ts} seems too old");
}
#[tokio::test]
async fn interval_ticks_twice() {
let mut iv = interval(Duration::from_millis(1));
iv.tick().await;
iv.tick().await;
}
#[tokio::test]
async fn spawn_fires() {
let (tx, rx) = tokio::sync::oneshot::channel();
spawn(async move {
let _ = tx.send(42);
});
let val = rx.await.unwrap();
assert_eq!(val, 42);
}
}