1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122
//! Resolution adapters for timers.
use core::time::Duration;
/// A timer resolution.
///
/// A coarser resolution might improve scheduling performance
/// of tasks where delays would exceed the `2^32` steps with finer resolutions.
///
/// Note that with the current design it's not possible
/// to know when the earliest next task is scheduled,
/// only which buckets are empty, thus resource saving
/// optimizations can only take buckets into account.
/// E.g. using seconds as a resolution will always
/// cause a busy wait as long as there is a task
/// that will expire within the next `255` seconds.
pub trait Resolution {
/// The maximum duration that can be represented as [`u32`]
/// at this resolution.
const MAX_DURATION: Duration;
/// Convert the given duration into timer cycle steps.
/// The given duration is guaranteed to be smaller than [`Resolution::MAX_DURATION`].
///
/// If `upper_bound` is set, the returned value should be rounded up.
fn cycle_steps(duration: &Duration, upper_bound: bool) -> u32;
/// Return total steps required for the given duration.
/// The returned steps should not be rounded up.
fn whole_steps(duration: &Duration) -> u128;
/// Convert the given steps to a duration.
fn steps_as_duration(steps: u64) -> Duration;
}
/// Microsecond resolution adapter.
///
/// Note that this kind of resolution is only
/// possible in very specific setups, as even a context switch
/// or allocation takes longer on Linux.
pub enum Microseconds {}
impl Resolution for Microseconds {
const MAX_DURATION: Duration = Duration::from_micros(u32::MAX as u64);
#[allow(clippy::cast_possible_truncation)]
fn cycle_steps(duration: &Duration, upper_bound: bool) -> u32 {
let mut steps = (duration.as_secs() * 1_000_000) as u32;
let micros = duration.subsec_micros();
steps += micros;
if upper_bound && duration.subsec_nanos() % 1_000 > 0 {
steps = steps.saturating_add(1);
}
steps
}
fn steps_as_duration(steps: u64) -> Duration {
Duration::from_micros(steps)
}
fn whole_steps(duration: &Duration) -> u128 {
duration.as_nanos() / 1_000
}
}
/// Millisecond resolution adapter.
///
/// The default resolution for `ora`
/// that can be achieved most of the time.
pub enum Milliseconds {}
impl Resolution for Milliseconds {
const MAX_DURATION: Duration = Duration::from_millis(u32::MAX as u64);
#[allow(clippy::cast_possible_truncation)]
fn cycle_steps(duration: &Duration, upper_bound: bool) -> u32 {
let mut steps = (duration.as_secs() * 1000) as u32;
let ms = duration.subsec_millis();
steps += ms;
if upper_bound && duration.subsec_nanos() % 1_000_000 > 0 {
steps = steps.saturating_add(1);
}
steps
}
fn steps_as_duration(steps: u64) -> Duration {
Duration::from_millis(steps)
}
fn whole_steps(duration: &Duration) -> u128 {
duration.as_nanos() / 1_000_000
}
}
/// Second resolution adapter.
///
/// Use this if the use-case involves delays often exceeding `2^32`
/// milliseconds (approx. 50 days) and you don't need millisecond granularity.
pub enum Seconds {}
impl Resolution for Seconds {
const MAX_DURATION: Duration = Duration::from_secs(u32::MAX as u64);
#[allow(clippy::cast_possible_truncation)]
fn cycle_steps(duration: &Duration, upper_bound: bool) -> u32 {
let mut steps = duration.as_secs() as u32;
if upper_bound && duration.subsec_nanos() > 0 {
steps = steps.saturating_add(1);
}
steps
}
fn steps_as_duration(steps: u64) -> Duration {
Duration::from_secs(steps)
}
fn whole_steps(duration: &Duration) -> u128 {
duration.as_nanos() / 1_000_000_000
}
}