use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use crate::error::{OcasError, Result};
#[derive(Debug, Clone)]
pub struct Fuel {
remaining: Arc<AtomicUsize>,
}
impl Fuel {
pub fn new(budget: usize) -> Self {
Self {
remaining: Arc::new(AtomicUsize::new(budget)),
}
}
pub fn consume(&self, n: usize) {
loop {
let cur = self.remaining.load(Ordering::Relaxed);
let next = cur.saturating_sub(n);
if self
.remaining
.compare_exchange_weak(cur, next, Ordering::Relaxed, Ordering::Relaxed)
.is_ok()
{
break;
}
}
}
pub fn check(&self) -> Result<()> {
if self.remaining.load(Ordering::Relaxed) == 0 {
Err(OcasError::OutOfFuel)
} else {
Ok(())
}
}
pub fn remaining(&self) -> usize {
self.remaining.load(Ordering::Relaxed)
}
}
impl Default for Fuel {
fn default() -> Self {
Self::new(1_000_000)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn consume_then_check() {
let f = Fuel::new(3);
assert!(f.check().is_ok());
f.consume(2);
assert!(f.check().is_ok());
f.consume(1);
assert!(f.check().is_err());
f.consume(10);
assert!(f.check().is_err());
}
#[test]
fn remaining_is_monotone() {
let f = Fuel::new(10);
assert_eq!(f.remaining(), 10);
f.consume(3);
assert_eq!(f.remaining(), 7);
f.consume(100); assert_eq!(f.remaining(), 0);
}
#[test]
fn clone_shares_counter_decrements() {
let f = Fuel::new(5);
let g = f.clone();
f.consume(2);
assert_eq!(g.remaining(), 3);
g.consume(3);
assert!(f.check().is_err());
}
#[test]
fn zero_budget_immediately_exhausted() {
let f = Fuel::new(0);
assert!(f.check().is_err());
assert_eq!(f.remaining(), 0);
}
#[test]
fn consume_zero_is_noop() {
let f = Fuel::new(5);
f.consume(0);
assert_eq!(f.remaining(), 5);
assert!(f.check().is_ok());
}
#[test]
fn default_has_generous_budget() {
let f = Fuel::default();
assert!(f.remaining() > 0);
assert!(f.check().is_ok());
}
}