use std::future::Future;
use std::mem::{self, MaybeUninit};
use std::pin::Pin;
use web_time_compat::{Duration, Instant, SystemTime};
use dyn_clone::DynClone;
use educe::Educe;
use paste::paste;
use crate::{CoarseInstant, CoarseTimeProvider, SleepProvider};
#[allow(unused_macros)] macro_rules! if_preferred_runtime {{ [$($y:tt)*] [$($n:tt)*] } => { $($n)* }}
#[cfg(all(
any(feature = "native-tls", feature = "rustls"),
any(feature = "async-std", feature = "tokio"),
))]
macro_rules! if_preferred_runtime {{ [$($y:tt)*] [$($n:tt)*] } => { $($y)* }}
if_preferred_runtime! {[
use crate::PreferredRuntime;
] [
#[derive(Clone, Debug)]
enum PreferredRuntime {}
]}
macro_rules! with_preferred_runtime {{ $p:ident; $($then:tt)* } => {
if_preferred_runtime!([ $($then)* ] [ match *$p {} ])
}}
type DynSleepFuture = Pin<Box<dyn Future<Output = ()> + Send + 'static>>;
#[allow(clippy::missing_docs_in_private_items)]
trait DynProvider: DynClone + Send + Sync + 'static {
fn dyn_now(&self) -> Instant;
fn dyn_wallclock(&self) -> SystemTime;
fn dyn_sleep(&self, duration: Duration) -> DynSleepFuture;
fn dyn_block_advance(&self, reason: String);
fn dyn_release_advance(&self, _reason: String);
fn dyn_allow_one_advance(&self, duration: Duration);
fn dyn_now_coarse(&self) -> CoarseInstant;
}
dyn_clone::clone_trait_object!(DynProvider);
#[derive(Clone, Debug)]
pub struct DynTimeProvider(Impl);
#[derive(Clone, Educe)]
#[educe(Debug)]
enum Impl {
Preferred(PreferredRuntime),
Dyn(#[educe(Debug(ignore))] Box<dyn DynProvider>),
}
impl DynTimeProvider {
pub fn new<R: SleepProvider + CoarseTimeProvider>(runtime: R) -> Self {
let runtime = match downcast_value(runtime) {
Ok(x) => return x,
Err(x) => x,
};
let imp = match downcast_value(runtime) {
Ok(preferred) => Impl::Preferred(preferred),
Err(other) => Impl::Dyn(Box::new(other) as _),
};
DynTimeProvider(imp)
}
}
macro_rules! dyn_impl_methods { { $(
fn $name:ident(
,
$( $param:ident: $ptype:ty ),*
) -> $ret:ty;
)* } => { paste! { $(
fn [<dyn_ $name>](
&self,
$( $param: $ptype, )*
)-> $ret {
self.$name( $($param,)* )
}
)* } } }
impl<R: SleepProvider + CoarseTimeProvider> DynProvider for R {
dyn_impl_methods! {
fn now(,) -> Instant;
fn wallclock(,) -> SystemTime;
fn block_advance(, reason: String) -> ();
fn release_advance(, reason: String) -> ();
fn allow_one_advance(, duration: Duration) -> ();
fn now_coarse(,) -> CoarseInstant;
}
fn dyn_sleep(&self, duration: Duration) -> DynSleepFuture {
Box::pin(self.sleep(duration))
}
}
macro_rules! pub_impl_methods { { $(
fn $name:ident $( [ $($generics:tt)* ] )? (
,
$( $param:ident: $ptype:ty ),*
) -> $ret:ty;
)* } => { paste! { $(
fn $name $( < $($generics)* > )?(
&self,
$( $param: $ptype, )*
)-> $ret {
match &self.0 {
Impl::Preferred(p) => with_preferred_runtime!(p; p.$name( $($param,)* )),
Impl::Dyn(p) => p.[<dyn_ $name>]( $($param .into() ,)? ),
}
}
)* } } }
impl SleepProvider for DynTimeProvider {
pub_impl_methods! {
fn now(,) -> Instant;
fn wallclock(,) -> SystemTime;
fn block_advance[R: Into<String>](, reason: R) -> ();
fn release_advance[R: Into<String>](, reason: R) -> ();
fn allow_one_advance(, duration: Duration) -> ();
}
type SleepFuture = DynSleepFuture;
fn sleep(&self, duration: Duration) -> DynSleepFuture {
match &self.0 {
Impl::Preferred(p) => with_preferred_runtime!(p; Box::pin(p.sleep(duration))),
Impl::Dyn(p) => p.dyn_sleep(duration),
}
}
}
impl CoarseTimeProvider for DynTimeProvider {
pub_impl_methods! {
fn now_coarse(,) -> CoarseInstant;
}
}
fn downcast_value<I: std::any::Any, O: Sized + 'static>(input: I) -> Result<O, I> {
let mut input = MaybeUninit::new(input);
let mut_ref: &mut I = unsafe { input.assume_init_mut() };
match <dyn std::any::Any>::downcast_mut(mut_ref) {
Some::<&mut O>(output) => {
let output = output as *mut O;
let output: O = unsafe { output.read() };
#[allow(clippy::drop_non_drop)] mem::drop::<MaybeUninit<I>>(input);
Ok(output)
}
None => Err(
unsafe { input.assume_init() },
),
}
}
#[cfg(test)]
mod test {
#![allow(clippy::bool_assert_comparison)]
#![allow(clippy::clone_on_copy)]
#![allow(clippy::dbg_macro)]
#![allow(clippy::mixed_attributes_style)]
#![allow(clippy::print_stderr)]
#![allow(clippy::print_stdout)]
#![allow(clippy::single_char_pattern)]
#![allow(clippy::unwrap_used)]
#![allow(clippy::unchecked_time_subtraction)]
#![allow(clippy::useless_vec)]
#![allow(clippy::needless_pass_by_value)]
#![allow(clippy::useless_format)]
use super::*;
use std::fmt::{Debug, Display};
use std::hint::black_box;
fn try_downcast_string<S: Display + Debug + 'static>(x: S) -> Result<String, S> {
black_box(downcast_value(black_box(x)))
}
#[test]
fn check_downcast_value() {
assert_eq!(try_downcast_string(format!("hi")).unwrap(), format!("hi"));
assert_eq!(try_downcast_string("hi").unwrap_err().to_string(), "hi");
}
#[test]
fn check_downcast_dropcount() {
#[derive(Debug, derive_more::Display)]
#[display("{self:?}")]
struct DropCounter(u32);
fn try_downcast_dc(x: impl Debug + 'static) -> Result<DropCounter, impl Debug + 'static> {
black_box(downcast_value(black_box(x)))
}
impl Drop for DropCounter {
fn drop(&mut self) {
let _: u32 = self.0.checked_sub(1).unwrap();
}
}
let dc = DropCounter(0);
let mut dc: DropCounter = try_downcast_dc(dc).unwrap();
assert_eq!(dc.0, 0);
dc.0 = 1;
let dc = DropCounter(0);
let mut dc: DropCounter = try_downcast_string(dc).unwrap_err();
assert_eq!(dc.0, 0);
dc.0 = 1;
}
if_preferred_runtime! {[
#[test]
fn dyn_time_provider_from_dyn_time_provider() {
let x = DynTimeProvider::new(PreferredRuntime::create().unwrap());
fn new_provider<R: SleepProvider + CoarseTimeProvider>(runtime: R) -> DynTimeProvider {
DynTimeProvider::new(runtime)
}
let x = new_provider(x);
assert!(matches!(x, DynTimeProvider(Impl::Preferred(_))));
}
] [
]}
}