use std::cell::RefCell;
std::thread_local! {
static COUNTER: RefCell<u64> = RefCell::new(0);
}
fn regenerate(c: &RefCell<u64>) -> u64 {
use rand::RngCore;
let mut rng = rand::thread_rng();
loop {
let rnd = rng.next_u64();
if rnd > 0 {
c.replace(rnd);
return rnd;
}
}
}
fn observe(weight: u64) -> bool {
COUNTER.with(|c| {
let current = *c.borrow();
if current > weight {
c.replace(current - weight);
return false;
}
let updated = regenerate(c);
if current > 0 {
return true;
}
if updated > weight {
c.replace(updated - weight);
return false;
}
regenerate(c);
true
})
}
#[derive(Clone, Copy, Debug)]
#[repr(transparent)]
pub(crate) struct PeriodicTrigger {
scale: u64,
}
impl PeriodicTrigger {
pub fn new(mut period: u64) -> PeriodicTrigger {
if period == 0 {
period = 1;
}
let scale = (u64::MAX / period) + ((u64::MAX % period) > 0) as u64;
PeriodicTrigger { scale }
}
#[inline(always)]
pub fn event(self) -> bool {
self.weighted_event(1)
}
#[inline(always)]
pub fn weighted_event(self, count: u64) -> bool {
observe(self.scale.saturating_mul(count))
}
}
#[test]
fn smoke_test() {
let trigger = PeriodicTrigger::new(10);
for _ in 0..10 {
assert!((0..10).any(|_| trigger.event()))
}
}
#[test]
fn test_delay_until_trigger() {
let trigger = PeriodicTrigger::new(10);
let mut max_delay = 0;
for _ in 0..200 {
let mut triggered = false;
for i in 1..=10 {
triggered = trigger.event();
if triggered {
max_delay = max_delay.max(i);
break;
}
}
assert!(triggered);
}
assert!(max_delay >= 9);
}
#[test]
fn test_weighted_delay_until_trigger() {
let trigger = PeriodicTrigger::new(10);
let mut max_delay = 0;
for _ in 0..200 {
let mut triggered = false;
for i in 1..=5 {
triggered = trigger.weighted_event(2);
if triggered {
max_delay = max_delay.max(i);
break;
}
}
assert!(triggered);
}
assert!(max_delay >= 4);
}
#[test]
fn test_weighted_always_fire() {
let trigger = PeriodicTrigger::new(20);
for _ in 0..10 {
assert!(trigger.weighted_event(20));
}
}
#[test]
fn test_zero_period() {
let trigger = PeriodicTrigger::new(0);
for _ in 0..10 {
assert!(trigger.event());
}
}
#[test]
fn test_one_period() {
let trigger = PeriodicTrigger::new(1);
for _ in 0..10 {
assert!(trigger.event());
}
}
#[test]
fn test_infinity_period() {
let trigger = PeriodicTrigger::new(u64::MAX);
for _ in 0..1000 {
assert!(!trigger.event());
}
}