diff --git a/Cargo.toml b/Cargo.toml index 4e248eb..1df71b8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,7 @@ json = ["dep:serde", "dep:serde_json"] anyhow.workspace = true async-task.workspace = true bytes.workspace = true +cfg-if.workspace = true futures-lite.workspace = true http-body-util.workspace = true http-body.workspace = true @@ -71,6 +72,7 @@ anyhow = "1" async-task = "4.7" axum = { version = "0.8.6", default-features = false } bytes = "1.10.1" +cfg-if = "1" cargo_metadata = "0.22" clap = { version = "4.5.26", features = ["derive"] } futures-core = "0.3.19" diff --git a/src/http.rs b/src/http.rs new file mode 100644 index 0000000..ed2c4a2 --- /dev/null +++ b/src/http.rs @@ -0,0 +1,3 @@ +//! HTTP networking support +//! +pub use crate::sys::http::*; diff --git a/src/io/mod.rs b/src/io/mod.rs index 0f34b1b..1f360ac 100644 --- a/src/io/mod.rs +++ b/src/io/mod.rs @@ -5,18 +5,15 @@ mod cursor; mod empty; mod read; mod seek; -mod stdio; -mod streams; mod write; pub use crate::runtime::AsyncPollable; +pub use crate::sys::io::*; pub use copy::*; pub use cursor::*; pub use empty::*; pub use read::*; pub use seek::*; -pub use stdio::*; -pub use streams::*; pub use write::*; /// The error type for I/O operations. diff --git a/src/lib.rs b/src/lib.rs index ebc673d..e4edc55 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -55,6 +55,9 @@ //! These are unique capabilities provided by WASI 0.2, and because this library //! is specific to that are exposed from here. +#[allow(unreachable_pub)] +mod sys; + pub mod future; #[macro_use] pub mod http; diff --git a/src/net.rs b/src/net.rs new file mode 100644 index 0000000..6ddff7d --- /dev/null +++ b/src/net.rs @@ -0,0 +1,3 @@ +//! Async network abstractions. + +pub use crate::sys::net::*; diff --git a/src/rand.rs b/src/rand.rs new file mode 100644 index 0000000..3528400 --- /dev/null +++ b/src/rand.rs @@ -0,0 +1,3 @@ +//! Random number generation. + +pub use crate::sys::rand::*; diff --git a/src/runtime.rs b/src/runtime.rs new file mode 100644 index 0000000..71d3acc --- /dev/null +++ b/src/runtime.rs @@ -0,0 +1,10 @@ +//! Async event loop support. +//! +//! The way to use this is to call [`block_on()`]. Inside the future, [`Reactor::current`] +//! will give an instance of the [`Reactor`] running the event loop, which can be +//! to [`AsyncPollable::wait_for`] instances of +//! [`wasip2::Pollable`](https://docs.rs/wasi/latest/wasi/io/poll/struct.Pollable.html). +//! This will automatically wait for the futures to resolve, and call the +//! necessary wakers to work. + +pub use crate::sys::runtime::*; diff --git a/src/sys/mod.rs b/src/sys/mod.rs new file mode 100644 index 0000000..cbb4385 --- /dev/null +++ b/src/sys/mod.rs @@ -0,0 +1,40 @@ +//! Platform-specific backends. +//! +//! Each supported target provides an implementation under `src/sys/`, selected +//! here by a single `cfg-if`. The crate-root modules (`crate::time`, +//! `crate::io`, ...) are target-agnostic facades, free of `#[cfg]`, written +//! against the `crate::sys::*` items the selected backend provides. There is no +//! shared `trait`: backends are duck-typed in the `std::sys` style, and the +//! `const _` assertions below check the shapes the facades depend on. +//! +//! Backend modules: `time`, `io`, `net`, `http`, `rand`, `runtime`. The reified +//! pollable types (`AsyncPollable`, `WaitFor`) are p2-only and intentionally +//! left out of the common contract; once a second backend lands they become a +//! localized escape hatch rather than facade `#[cfg]`s. + +cfg_if::cfg_if! { + if #[cfg(all(target_os = "wasi", target_env = "p2"))] { + mod p2; + use p2 as backend; + } else { + compile_error!("unsupported target: wstd only compiles on `wasm32-wasip2`"); + } +} + +pub use backend::*; + +// Check the selected backend provides the shapes the facades rely on, so drift +// fails here instead of deep inside a facade. +const _: fn() = || { + fn assert_async_read() {} + fn assert_async_write() {} + fn assert_sleep_future>() {} + + assert_async_read::(); + assert_async_write::(); + assert_sleep_future::(); + + let _: fn() -> crate::sys::time::MonotonicInstant = crate::sys::time::now; + let _: fn(crate::sys::time::MonotonicInstant) -> crate::sys::time::Sleep = + crate::sys::time::sleep_until; +}; diff --git a/src/http/body.rs b/src/sys/p2/http/body.rs similarity index 99% rename from src/http/body.rs rename to src/sys/p2/http/body.rs index 97b093c..db8e9ef 100644 --- a/src/http/body.rs +++ b/src/sys/p2/http/body.rs @@ -1,8 +1,5 @@ -use crate::http::{ - Error, HeaderMap, - error::Context as _, - fields::{header_map_from_wasi, header_map_to_wasi}, -}; +use super::fields::{header_map_from_wasi, header_map_to_wasi}; +use crate::http::{Error, HeaderMap, error::Context as _}; use crate::io::{AsyncInputStream, AsyncOutputStream}; use crate::runtime::{AsyncPollable, Reactor, WaitFor}; diff --git a/src/http/client.rs b/src/sys/p2/http/client.rs similarity index 100% rename from src/http/client.rs rename to src/sys/p2/http/client.rs diff --git a/src/http/error.rs b/src/sys/p2/http/error.rs similarity index 100% rename from src/http/error.rs rename to src/sys/p2/http/error.rs diff --git a/src/http/fields.rs b/src/sys/p2/http/fields.rs similarity index 100% rename from src/http/fields.rs rename to src/sys/p2/http/fields.rs diff --git a/src/http/method.rs b/src/sys/p2/http/method.rs similarity index 100% rename from src/http/method.rs rename to src/sys/p2/http/method.rs diff --git a/src/http/mod.rs b/src/sys/p2/http/mod.rs similarity index 100% rename from src/http/mod.rs rename to src/sys/p2/http/mod.rs diff --git a/src/http/request.rs b/src/sys/p2/http/request.rs similarity index 100% rename from src/http/request.rs rename to src/sys/p2/http/request.rs diff --git a/src/http/response.rs b/src/sys/p2/http/response.rs similarity index 96% rename from src/http/response.rs rename to src/sys/p2/http/response.rs index 2ab8d87..44f264b 100644 --- a/src/http/response.rs +++ b/src/sys/p2/http/response.rs @@ -1,9 +1,10 @@ use http::StatusCode; use wasip2::http::types::IncomingResponse; +use super::fields::header_map_from_wasi; +use crate::http::HeaderMap; use crate::http::body::{Body, BodyHint}; use crate::http::error::Error; -use crate::http::fields::{HeaderMap, header_map_from_wasi}; pub use http::response::{Builder, Response}; diff --git a/src/http/scheme.rs b/src/sys/p2/http/scheme.rs similarity index 100% rename from src/http/scheme.rs rename to src/sys/p2/http/scheme.rs diff --git a/src/http/server.rs b/src/sys/p2/http/server.rs similarity index 100% rename from src/http/server.rs rename to src/sys/p2/http/server.rs diff --git a/src/sys/p2/io/mod.rs b/src/sys/p2/io/mod.rs new file mode 100644 index 0000000..9323962 --- /dev/null +++ b/src/sys/p2/io/mod.rs @@ -0,0 +1,5 @@ +mod stdio; +mod streams; + +pub use stdio::*; +pub use streams::*; diff --git a/src/io/stdio.rs b/src/sys/p2/io/stdio.rs similarity index 98% rename from src/io/stdio.rs rename to src/sys/p2/io/stdio.rs index b2ac153..fa183a3 100644 --- a/src/io/stdio.rs +++ b/src/sys/p2/io/stdio.rs @@ -1,4 +1,4 @@ -use super::{AsyncInputStream, AsyncOutputStream, AsyncRead, AsyncWrite, Result}; +use crate::io::{AsyncInputStream, AsyncOutputStream, AsyncRead, AsyncWrite, Result}; use std::cell::LazyCell; use wasip2::cli::terminal_input::TerminalInput; use wasip2::cli::terminal_output::TerminalOutput; diff --git a/src/io/streams.rs b/src/sys/p2/io/streams.rs similarity index 99% rename from src/io/streams.rs rename to src/sys/p2/io/streams.rs index 3676d21..5f95ca5 100644 --- a/src/io/streams.rs +++ b/src/sys/p2/io/streams.rs @@ -1,5 +1,5 @@ -use super::{AsyncPollable, AsyncRead, AsyncWrite}; -use crate::runtime::WaitFor; +use crate::io::{AsyncRead, AsyncWrite}; +use crate::runtime::{AsyncPollable, WaitFor}; use std::future::{Future, poll_fn}; use std::pin::Pin; use std::sync::{Mutex, OnceLock}; diff --git a/src/sys/p2/mod.rs b/src/sys/p2/mod.rs new file mode 100644 index 0000000..f7f323a --- /dev/null +++ b/src/sys/p2/mod.rs @@ -0,0 +1,8 @@ +//! The wasip2 (`wasm32-wasip2`) backend. + +pub mod http; +pub mod io; +pub mod net; +pub mod rand; +pub mod runtime; +pub mod time; diff --git a/src/net/mod.rs b/src/sys/p2/net/mod.rs similarity index 100% rename from src/net/mod.rs rename to src/sys/p2/net/mod.rs diff --git a/src/net/tcp_listener.rs b/src/sys/p2/net/tcp_listener.rs similarity index 100% rename from src/net/tcp_listener.rs rename to src/sys/p2/net/tcp_listener.rs diff --git a/src/net/tcp_stream.rs b/src/sys/p2/net/tcp_stream.rs similarity index 100% rename from src/net/tcp_stream.rs rename to src/sys/p2/net/tcp_stream.rs diff --git a/src/rand/mod.rs b/src/sys/p2/rand/mod.rs similarity index 100% rename from src/rand/mod.rs rename to src/sys/p2/rand/mod.rs diff --git a/src/runtime/block_on.rs b/src/sys/p2/runtime/block_on.rs similarity index 100% rename from src/runtime/block_on.rs rename to src/sys/p2/runtime/block_on.rs diff --git a/src/runtime/mod.rs b/src/sys/p2/runtime/mod.rs similarity index 96% rename from src/runtime/mod.rs rename to src/sys/p2/runtime/mod.rs index 24b9fc2..4c82015 100644 --- a/src/runtime/mod.rs +++ b/src/sys/p2/runtime/mod.rs @@ -8,7 +8,7 @@ //! necessary wakers to work. #![deny(missing_debug_implementations, nonstandard_style)] -#![warn(missing_docs, unreachable_pub)] +#![warn(missing_docs)] mod block_on; mod reactor; diff --git a/src/runtime/reactor.rs b/src/sys/p2/runtime/reactor.rs similarity index 100% rename from src/runtime/reactor.rs rename to src/sys/p2/runtime/reactor.rs diff --git a/src/sys/p2/time/mod.rs b/src/sys/p2/time/mod.rs new file mode 100644 index 0000000..92b8983 --- /dev/null +++ b/src/sys/p2/time/mod.rs @@ -0,0 +1,78 @@ +//! Monotonic and system clocks for the wasip2 backend. +//! +//! This is the platform half of the [`crate::time`] facade. The facade owns the +//! portable `Duration`/`Instant`/`Timer` types and all of their arithmetic; +//! this module provides only the primitives that genuinely depend on the WASI +//! 0.2 clocks. See [`crate::sys`] for the full backend contract. + +use std::future::Future; +use std::pin::Pin; +use std::task::{Context, Poll}; +use wasip2::clocks::{monotonic_clock, wall_clock}; + +use crate::runtime::{Reactor, WaitFor}; + +/// A measurement of the monotonic clock, in nanoseconds. +/// +/// The facade's `Instant` wraps this. Keeping it a plain integer lets the +/// facade own all time arithmetic without coupling to the backend. +pub type MonotonicInstant = monotonic_clock::Instant; + +/// A span of monotonic-clock time, in nanoseconds. +pub type MonotonicDuration = monotonic_clock::Duration; + +/// Return the current monotonic-clock instant. +pub fn now() -> MonotonicInstant { + monotonic_clock::now() +} + +/// A measurement of the system clock, useful for talking to external entities +/// like the file system or other processes. May be converted losslessly to a +/// more useful `std::time::SystemTime` to provide more methods. +#[derive(Debug, Clone, Copy)] +#[allow(dead_code)] +pub struct SystemTime(wall_clock::Datetime); + +impl SystemTime { + pub fn now() -> Self { + Self(wall_clock::now()) + } +} + +impl From for std::time::SystemTime { + fn from(st: SystemTime) -> Self { + std::time::SystemTime::UNIX_EPOCH + + std::time::Duration::from_secs(st.0.seconds) + + std::time::Duration::from_nanos(st.0.nanoseconds.into()) + } +} + +/// A future that resolves once the monotonic clock reaches a deadline. +/// +/// Created by [`sleep_until`]. This is the backend `Sleep` type named by the +/// facade's `Timer`/`Wait`; on p2 it is a thin wrapper over a reactor-scheduled +/// `monotonic-clock` pollable. +#[must_use = "futures do nothing unless polled or .awaited"] +#[derive(Debug)] +pub struct Sleep { + wait_for: WaitFor, +} + +impl Future for Sleep { + type Output = (); + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + Pin::new(&mut self.wait_for).poll(cx) + } +} + +/// Create a [`Sleep`] future that resolves when the monotonic clock reaches +/// `deadline`. +/// +/// Must be called from within [`crate::runtime::block_on`]. +pub fn sleep_until(deadline: MonotonicInstant) -> Sleep { + let pollable = Reactor::current().schedule(monotonic_clock::subscribe_instant(deadline)); + Sleep { + wait_for: pollable.wait_for(), + } +} diff --git a/src/time/duration.rs b/src/time.rs similarity index 50% rename from src/time/duration.rs rename to src/time.rs index 7f67ceb..043bb03 100644 --- a/src/time/duration.rs +++ b/src/time.rs @@ -1,7 +1,28 @@ -use super::{Instant, Wait}; -use std::future::IntoFuture; +//! Async time interfaces. +//! +//! This module is a target-agnostic *facade*: it owns the portable +//! `Duration`/`Instant`/`Timer`/`Interval` types and all of their arithmetic, +//! and is written once against the small clock contract each backend provides +//! under `crate::sys::time` (see [`crate::sys`]). The only backend-specific +//! type re-exported here is [`SystemTime`]. + +use pin_project_lite::pin_project; +use std::future::{Future, IntoFuture}; use std::ops::{Add, AddAssign, Sub, SubAssign}; -use wasip2::clocks::monotonic_clock; +use std::pin::Pin; +use std::task::{Context, Poll}; + +use crate::iter::AsyncIterator; + +pub use crate::sys::time::SystemTime; + +pub(crate) mod utils { + use std::io; + + pub(crate) fn timeout_err(msg: &'static str) -> io::Error { + io::Error::new(io::ErrorKind::TimedOut, msg) + } +} /// A Duration type to represent a span of time, typically used for system /// timeouts. @@ -10,7 +31,7 @@ use wasip2::clocks::monotonic_clock; /// without coherence issues, just like if we were implementing this in the /// stdlib. #[derive(Debug, PartialEq, PartialOrd, Ord, Eq, Hash, Clone, Copy)] -pub struct Duration(pub(crate) monotonic_clock::Duration); +pub struct Duration(pub(crate) crate::sys::time::MonotonicDuration); impl Duration { /// Creates a new `Duration` from the specified number of whole seconds and /// additional nanoseconds. @@ -162,6 +183,172 @@ impl IntoFuture for Duration { } } +/// A measurement of a monotonically nondecreasing clock. Opaque and useful only +/// with Duration. +/// +/// This type wraps `std::time::Duration` so we can implement traits on it +/// without coherence issues, just like if we were implementing this in the +/// stdlib. +#[derive(Debug, PartialEq, PartialOrd, Ord, Eq, Hash, Clone, Copy)] +pub struct Instant(pub(crate) crate::sys::time::MonotonicInstant); + +impl Instant { + /// Returns an instant corresponding to "now". + /// + /// # Examples + /// + /// ```no_run + /// use wstd::time::Instant; + /// + /// let now = Instant::now(); + /// ``` + #[must_use] + pub fn now() -> Self { + Instant(crate::sys::time::now()) + } + + /// Returns the amount of time elapsed from another instant to this one, or zero duration if + /// that instant is later than this one. + pub fn duration_since(&self, earlier: Instant) -> Duration { + Duration::from_nanos(self.0.saturating_sub(earlier.0)) + } + + /// Returns the amount of time elapsed since this instant. + pub fn elapsed(&self) -> Duration { + Instant::now().duration_since(*self) + } +} + +impl Add for Instant { + type Output = Self; + + fn add(self, rhs: Duration) -> Self::Output { + Self(self.0 + rhs.0) + } +} + +impl AddAssign for Instant { + fn add_assign(&mut self, rhs: Duration) { + *self = Self(self.0 + rhs.0) + } +} + +impl Sub for Instant { + type Output = Self; + + fn sub(self, rhs: Duration) -> Self::Output { + Self(self.0 - rhs.0) + } +} + +impl SubAssign for Instant { + fn sub_assign(&mut self, rhs: Duration) { + *self = Self(self.0 - rhs.0) + } +} + +impl IntoFuture for Instant { + type Output = Instant; + + type IntoFuture = Wait; + + fn into_future(self) -> Self::IntoFuture { + crate::task::sleep_until(self) + } +} + +/// An async iterator representing notifications at fixed interval. +pub fn interval(duration: Duration) -> Interval { + Interval { duration } +} + +/// An async iterator representing notifications at fixed interval. +/// +/// See the [`interval`] function for more. +#[derive(Debug)] +pub struct Interval { + duration: Duration, +} +impl AsyncIterator for Interval { + type Item = Instant; + + async fn next(&mut self) -> Option { + Some(Timer::after(self.duration).wait().await) + } +} + +/// A measurement that resolves at a deadline, or never. +/// +/// A `Timer` records *when* it should fire when it is constructed; each call to +/// [`Timer::wait`] then builds a fresh [`Wait`] future against the backend +/// clock. Because the deadline is captured up front, `wait` is repeatable and a +/// `Timer` can be polled into more than once. +#[derive(Debug)] +pub struct Timer(TimerKind); + +#[derive(Debug, Clone, Copy)] +enum TimerKind { + /// Never fires; the resulting [`Wait`] is pending forever. + Never, + /// Fires once the monotonic clock reaches this instant. + At(Instant), +} + +impl Timer { + /// Create a `Timer` that never fires. + pub fn never() -> Timer { + Timer(TimerKind::Never) + } + /// Create a `Timer` that fires at `deadline`. + pub fn at(deadline: Instant) -> Timer { + Timer(TimerKind::At(deadline)) + } + /// Create a `Timer` that fires `duration` from now. + /// + /// The deadline is computed at construction time, matching the behavior of + /// `std::time` and preserving it across repeated [`wait`](Timer::wait) + /// calls. + pub fn after(duration: Duration) -> Timer { + Timer(TimerKind::At(Instant::now() + duration)) + } + /// Reset the `Timer` to fire `duration` from now. + pub fn set_after(&mut self, duration: Duration) { + *self = Self::after(duration); + } + /// Create a future that resolves when the `Timer` fires. + pub fn wait(&self) -> Wait { + let sleep = match self.0 { + TimerKind::Never => None, + TimerKind::At(deadline) => Some(crate::sys::time::sleep_until(deadline.0)), + }; + Wait { sleep } + } +} + +pin_project! { + /// Future created by [`Timer::wait`] + #[must_use = "futures do nothing unless polled or .awaited"] + pub struct Wait { + #[pin] + sleep: Option + } +} + +impl Future for Wait { + type Output = Instant; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = self.project(); + match this.sleep.as_pin_mut() { + None => Poll::Pending, + Some(sleep) => match sleep.poll(cx) { + Poll::Pending => Poll::Pending, + Poll::Ready(()) => Poll::Ready(Instant::now()), + }, + } + } +} + #[cfg(test)] mod tests { use super::*; @@ -213,4 +400,34 @@ mod tests { assert_eq!(Duration::from_secs_f64(159.1).as_secs(), 159); assert_eq!(Duration::from_secs_f32(159.1).as_secs(), 159); } + + #[test] + fn test_duration_since() { + let x = Instant::now(); + let d = Duration::new(456, 789); + let y = x + d; + assert_eq!(y.duration_since(x), d); + } + + async fn debug_duration(what: &str, f: impl Future) { + let start = Instant::now(); + let now = f.await; + let d = now.duration_since(start); + let d: std::time::Duration = d.into(); + println!("{what} awaited for {} s", d.as_secs_f32()); + } + + #[test] + fn timer_now() { + crate::runtime::block_on(debug_duration("timer_now", async { + Timer::at(Instant::now()).wait().await + })); + } + + #[test] + fn timer_after_100_milliseconds() { + crate::runtime::block_on(debug_duration("timer_after_100_milliseconds", async { + Timer::after(Duration::from_millis(100)).wait().await + })); + } } diff --git a/src/time/instant.rs b/src/time/instant.rs deleted file mode 100644 index 6e9cf97..0000000 --- a/src/time/instant.rs +++ /dev/null @@ -1,91 +0,0 @@ -use super::{Duration, Wait}; -use std::future::IntoFuture; -use std::ops::{Add, AddAssign, Sub, SubAssign}; -use wasip2::clocks::monotonic_clock; - -/// A measurement of a monotonically nondecreasing clock. Opaque and useful only -/// with Duration. -/// -/// This type wraps `std::time::Duration` so we can implement traits on it -/// without coherence issues, just like if we were implementing this in the -/// stdlib. -#[derive(Debug, PartialEq, PartialOrd, Ord, Eq, Hash, Clone, Copy)] -pub struct Instant(pub(crate) monotonic_clock::Instant); - -impl Instant { - /// Returns an instant corresponding to "now". - /// - /// # Examples - /// - /// ```no_run - /// use wstd::time::Instant; - /// - /// let now = Instant::now(); - /// ``` - #[must_use] - pub fn now() -> Self { - Instant(wasip2::clocks::monotonic_clock::now()) - } - - /// Returns the amount of time elapsed from another instant to this one, or zero duration if - /// that instant is later than this one. - pub fn duration_since(&self, earlier: Instant) -> Duration { - Duration::from_nanos(self.0.saturating_sub(earlier.0)) - } - - /// Returns the amount of time elapsed since this instant. - pub fn elapsed(&self) -> Duration { - Instant::now().duration_since(*self) - } -} - -impl Add for Instant { - type Output = Self; - - fn add(self, rhs: Duration) -> Self::Output { - Self(self.0 + rhs.0) - } -} - -impl AddAssign for Instant { - fn add_assign(&mut self, rhs: Duration) { - *self = Self(self.0 + rhs.0) - } -} - -impl Sub for Instant { - type Output = Self; - - fn sub(self, rhs: Duration) -> Self::Output { - Self(self.0 - rhs.0) - } -} - -impl SubAssign for Instant { - fn sub_assign(&mut self, rhs: Duration) { - *self = Self(self.0 - rhs.0) - } -} - -impl IntoFuture for Instant { - type Output = Instant; - - type IntoFuture = Wait; - - fn into_future(self) -> Self::IntoFuture { - crate::task::sleep_until(self) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_duration_since() { - let x = Instant::now(); - let d = Duration::new(456, 789); - let y = x + d; - assert_eq!(y.duration_since(x), d); - } -} diff --git a/src/time/mod.rs b/src/time/mod.rs deleted file mode 100644 index db0e1b3..0000000 --- a/src/time/mod.rs +++ /dev/null @@ -1,138 +0,0 @@ -//! Async time interfaces. - -pub(crate) mod utils; - -mod duration; -mod instant; -pub use duration::Duration; -pub use instant::Instant; - -use pin_project_lite::pin_project; -use std::future::Future; -use std::pin::Pin; -use std::task::{Context, Poll}; -use wasip2::clocks::{ - monotonic_clock::{subscribe_duration, subscribe_instant}, - wall_clock, -}; - -use crate::{ - iter::AsyncIterator, - runtime::{AsyncPollable, Reactor}, -}; - -/// A measurement of the system clock, useful for talking to external entities -/// like the file system or other processes. May be converted losslessly to a -/// more useful `std::time::SystemTime` to provide more methods. -#[derive(Debug, Clone, Copy)] -#[allow(dead_code)] -pub struct SystemTime(wall_clock::Datetime); - -impl SystemTime { - pub fn now() -> Self { - Self(wall_clock::now()) - } -} - -impl From for std::time::SystemTime { - fn from(st: SystemTime) -> Self { - std::time::SystemTime::UNIX_EPOCH - + std::time::Duration::from_secs(st.0.seconds) - + std::time::Duration::from_nanos(st.0.nanoseconds.into()) - } -} - -/// An async iterator representing notifications at fixed interval. -pub fn interval(duration: Duration) -> Interval { - Interval { duration } -} - -/// An async iterator representing notifications at fixed interval. -/// -/// See the [`interval`] function for more. -#[derive(Debug)] -pub struct Interval { - duration: Duration, -} -impl AsyncIterator for Interval { - type Item = Instant; - - async fn next(&mut self) -> Option { - Some(Timer::after(self.duration).wait().await) - } -} - -#[derive(Debug)] -pub struct Timer(Option); - -impl Timer { - pub fn never() -> Timer { - Timer(None) - } - pub fn at(deadline: Instant) -> Timer { - let pollable = Reactor::current().schedule(subscribe_instant(deadline.0)); - Timer(Some(pollable)) - } - pub fn after(duration: Duration) -> Timer { - let pollable = Reactor::current().schedule(subscribe_duration(duration.0)); - Timer(Some(pollable)) - } - pub fn set_after(&mut self, duration: Duration) { - *self = Self::after(duration); - } - pub fn wait(&self) -> Wait { - let wait_for = self.0.as_ref().map(AsyncPollable::wait_for); - Wait { wait_for } - } -} - -pin_project! { - /// Future created by [`Timer::wait`] - #[must_use = "futures do nothing unless polled or .awaited"] - pub struct Wait { - #[pin] - wait_for: Option - } -} - -impl Future for Wait { - type Output = Instant; - - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let this = self.project(); - match this.wait_for.as_pin_mut() { - None => Poll::Pending, - Some(f) => match f.poll(cx) { - Poll::Pending => Poll::Pending, - Poll::Ready(()) => Poll::Ready(Instant::now()), - }, - } - } -} - -#[cfg(test)] -mod test { - use super::*; - - async fn debug_duration(what: &str, f: impl Future) { - let start = Instant::now(); - let now = f.await; - let d = now.duration_since(start); - let d: std::time::Duration = d.into(); - println!("{what} awaited for {} s", d.as_secs_f32()); - } - - #[test] - fn timer_now() { - crate::runtime::block_on(debug_duration("timer_now", async { - Timer::at(Instant::now()).wait().await - })); - } - - #[test] - fn timer_after_100_milliseconds() { - crate::runtime::block_on(debug_duration("timer_after_100_milliseconds", async { - Timer::after(Duration::from_millis(100)).wait().await - })); - } -} diff --git a/src/time/utils.rs b/src/time/utils.rs deleted file mode 100644 index e6e3993..0000000 --- a/src/time/utils.rs +++ /dev/null @@ -1,5 +0,0 @@ -use std::io; - -pub(crate) fn timeout_err(msg: &'static str) -> io::Error { - io::Error::new(io::ErrorKind::TimedOut, msg) -}