use core::{future::Future, hint::spin_loop};
use std::{
thread::{sleep, yield_now},
time::Duration,
};
pub const SPIN_LIMIT: u32 = 32;
pub const YIELD_LIMIT: u32 = 1024;
pub const SLEEP_MICROS: u64 = 50;
pub const SLEEP_DURATION: Duration = Duration::from_micros(SLEEP_MICROS);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum BackoffStage {
Spin,
Yield,
Sleep,
}
#[inline(always)]
const fn stage_of(step: u32) -> BackoffStage {
if step < SPIN_LIMIT {
BackoffStage::Spin
} else if step < YIELD_LIMIT {
BackoffStage::Yield
} else {
BackoffStage::Sleep
}
}
impl BackoffStage {
#[inline(always)]
pub const fn is_spin(&self) -> bool {
matches!(self, Self::Spin)
}
#[inline]
pub fn wait(&self) {
match self {
Self::Spin => spin_loop(),
Self::Yield => yield_now(),
Self::Sleep => sleep(SLEEP_DURATION),
}
}
#[inline]
pub fn wait_busy(&self) {
match self {
Self::Sleep => yield_now(),
stage => stage.wait(),
}
}
#[inline]
pub async fn wait_async<S: Future<Output = ()>>(&self, sleeper: impl FnOnce(Duration) -> S) {
match self {
Self::Sleep => sleeper(SLEEP_DURATION).await,
stage => stage.wait(),
}
}
}
#[derive(Debug, Default, Clone, Copy)]
pub struct Backoff {
step: u32,
}
impl Backoff {
#[inline(always)]
pub const fn new() -> Self {
Self { step: 0 }
}
#[inline(always)]
pub const fn step_count(&self) -> u32 {
self.step
}
#[inline(always)]
pub const fn stage(&self) -> BackoffStage {
stage_of(self.step)
}
#[inline]
pub fn snooze(&mut self) {
self.stage().wait();
self.advance();
}
#[inline(always)]
pub fn advance(&mut self) {
self.step = self.step.saturating_add(1);
}
#[inline(always)]
pub fn reset(&mut self) {
self.step = 0;
}
}
#[inline]
pub fn backoff(round: u32) {
stage_of(round).wait();
}