use core::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,
}
impl BackoffStage {
#[inline(always)]
pub const fn is_spin(&self) -> bool {
matches!(self, Self::Spin)
}
#[inline(always)]
pub const fn is_yield(&self) -> bool {
matches!(self, Self::Yield)
}
#[inline(always)]
pub const fn is_sleep(&self) -> bool {
matches!(self, Self::Sleep)
}
}
#[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 {
if self.step < SPIN_LIMIT {
BackoffStage::Spin
} else if self.step < YIELD_LIMIT {
BackoffStage::Yield
} else {
BackoffStage::Sleep
}
}
#[inline(always)]
pub const fn is_sleep(&self) -> bool {
self.step >= YIELD_LIMIT
}
#[inline]
pub fn snooze(&mut self) {
match self.stage() {
BackoffStage::Spin => spin_loop(),
BackoffStage::Yield => yield_now(),
BackoffStage::Sleep => sleep(SLEEP_DURATION),
}
self.step = self.step.saturating_add(1);
}
#[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) {
if round < SPIN_LIMIT {
spin_loop();
} else if round < YIELD_LIMIT {
yield_now();
} else {
sleep(SLEEP_DURATION);
}
}