use std::sync::atomic::{AtomicU64, Ordering};
use tracing::{debug, info, warn};
use crate::error::{ResourceError, ResourceResult};
#[derive(Debug, Clone)]
pub struct FuelConfig {
pub initial_fuel: u64,
pub allow_refuel: bool,
pub max_refuel: u64,
pub low_fuel_threshold: Option<u64>,
}
impl Default for FuelConfig {
fn default() -> Self {
Self {
initial_fuel: 1_000_000_000, allow_refuel: false,
max_refuel: 0,
low_fuel_threshold: None,
}
}
}
impl FuelConfig {
pub fn new(initial_fuel: u64) -> Self {
Self {
initial_fuel,
..Default::default()
}
}
pub fn with_refuel(mut self, max_refuel: u64) -> Self {
self.allow_refuel = true;
self.max_refuel = max_refuel;
self
}
pub fn with_low_fuel_threshold(mut self, threshold: u64) -> Self {
self.low_fuel_threshold = Some(threshold);
self
}
pub fn minimal() -> Self {
Self::new(10_000)
}
pub fn standard() -> Self {
Self::default()
}
pub fn generous() -> Self {
Self::new(10_000_000_000) }
}
pub type LowFuelCallback = Box<dyn Fn(u64) + Send + Sync>;
pub struct FuelManager {
config: FuelConfig,
total_consumed: AtomicU64,
exhaustion_count: AtomicU64,
refuel_count: AtomicU64,
total_refueled: AtomicU64,
}
impl FuelManager {
pub fn new(config: FuelConfig) -> Self {
info!(
initial_fuel = config.initial_fuel,
allow_refuel = config.allow_refuel,
"Created fuel manager"
);
Self {
config,
total_consumed: AtomicU64::new(0),
exhaustion_count: AtomicU64::new(0),
refuel_count: AtomicU64::new(0),
total_refueled: AtomicU64::new(0),
}
}
pub fn with_defaults() -> Self {
Self::new(FuelConfig::default())
}
pub fn initial_fuel(&self) -> u64 {
self.config.initial_fuel
}
pub fn refuel_allowed(&self) -> bool {
self.config.allow_refuel
}
pub fn max_refuel(&self) -> u64 {
self.config.max_refuel
}
pub fn record_consumption(&self, consumed: u64) {
self.total_consumed.fetch_add(consumed, Ordering::Relaxed);
debug!(consumed, total = self.total_consumed(), "Recorded fuel consumption");
}
pub fn record_exhaustion(&self) {
self.exhaustion_count.fetch_add(1, Ordering::Relaxed);
warn!(
total_exhaustions = self.exhaustion_count(),
"Fuel exhausted"
);
}
pub fn request_refuel(&self, requested: u64) -> ResourceResult<u64> {
if !self.config.allow_refuel {
return Err(ResourceError::RefuelDenied {
reason: "Refueling is not allowed".to_string(),
});
}
let amount = requested.min(self.config.max_refuel);
self.refuel_count.fetch_add(1, Ordering::Relaxed);
self.total_refueled.fetch_add(amount, Ordering::Relaxed);
debug!(requested, granted = amount, "Refuel granted");
Ok(amount)
}
pub fn total_consumed(&self) -> u64 {
self.total_consumed.load(Ordering::Relaxed)
}
pub fn exhaustion_count(&self) -> u64 {
self.exhaustion_count.load(Ordering::Relaxed)
}
pub fn refuel_count(&self) -> u64 {
self.refuel_count.load(Ordering::Relaxed)
}
pub fn total_refueled(&self) -> u64 {
self.total_refueled.load(Ordering::Relaxed)
}
pub fn reset_stats(&self) {
self.total_consumed.store(0, Ordering::Relaxed);
self.exhaustion_count.store(0, Ordering::Relaxed);
self.refuel_count.store(0, Ordering::Relaxed);
self.total_refueled.store(0, Ordering::Relaxed);
}
pub fn stats(&self) -> FuelStats {
FuelStats {
initial_fuel: self.config.initial_fuel,
total_consumed: self.total_consumed(),
exhaustion_count: self.exhaustion_count(),
refuel_count: self.refuel_count(),
total_refueled: self.total_refueled(),
}
}
}
impl std::fmt::Debug for FuelManager {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("FuelManager")
.field("config", &self.config)
.field("total_consumed", &self.total_consumed())
.field("exhaustion_count", &self.exhaustion_count())
.finish()
}
}
#[derive(Debug, Clone)]
pub struct FuelStats {
pub initial_fuel: u64,
pub total_consumed: u64,
pub exhaustion_count: u64,
pub refuel_count: u64,
pub total_refueled: u64,
}
impl FuelStats {
pub fn effective_consumed(&self) -> u64 {
self.total_consumed.saturating_sub(self.total_refueled)
}
pub fn had_exhaustions(&self) -> bool {
self.exhaustion_count > 0
}
}
#[derive(Debug, Clone, Copy)]
pub struct FuelCostEstimates {
pub per_instruction: u64,
pub per_memory_page: u64,
pub per_host_call: u64,
pub per_indirect_call: u64,
}
impl Default for FuelCostEstimates {
fn default() -> Self {
Self {
per_instruction: 1,
per_memory_page: 1000,
per_host_call: 100,
per_indirect_call: 10,
}
}
}
impl FuelCostEstimates {
pub fn estimate_instructions(&self, count: u64) -> u64 {
count * self.per_instruction
}
pub fn estimate_memory_pages(&self, pages: u64) -> u64 {
pages * self.per_memory_page
}
pub fn estimate_host_calls(&self, count: u64) -> u64 {
count * self.per_host_call
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_fuel_config_creation() {
let config = FuelConfig::new(1_000_000);
assert_eq!(config.initial_fuel, 1_000_000);
assert!(!config.allow_refuel);
}
#[test]
fn test_fuel_config_with_refuel() {
let config = FuelConfig::new(1_000_000).with_refuel(500_000);
assert!(config.allow_refuel);
assert_eq!(config.max_refuel, 500_000);
}
#[test]
fn test_fuel_manager_creation() {
let manager = FuelManager::new(FuelConfig::default());
assert_eq!(manager.total_consumed(), 0);
assert_eq!(manager.exhaustion_count(), 0);
}
#[test]
fn test_fuel_consumption_tracking() {
let manager = FuelManager::new(FuelConfig::default());
manager.record_consumption(1000);
manager.record_consumption(500);
assert_eq!(manager.total_consumed(), 1500);
}
#[test]
fn test_fuel_exhaustion_tracking() {
let manager = FuelManager::new(FuelConfig::default());
manager.record_exhaustion();
manager.record_exhaustion();
assert_eq!(manager.exhaustion_count(), 2);
}
#[test]
fn test_refuel_denied_by_default() {
let manager = FuelManager::new(FuelConfig::default());
let result = manager.request_refuel(1000);
assert!(result.is_err());
}
#[test]
fn test_refuel_allowed() {
let config = FuelConfig::new(1_000_000).with_refuel(500_000);
let manager = FuelManager::new(config);
let amount = manager.request_refuel(1000).unwrap();
assert_eq!(amount, 1000);
assert_eq!(manager.refuel_count(), 1);
}
#[test]
fn test_refuel_capped_at_max() {
let config = FuelConfig::new(1_000_000).with_refuel(500);
let manager = FuelManager::new(config);
let amount = manager.request_refuel(1000).unwrap();
assert_eq!(amount, 500); }
#[test]
fn test_stats() {
let config = FuelConfig::new(1_000_000).with_refuel(500_000);
let manager = FuelManager::new(config);
manager.record_consumption(5000);
manager.request_refuel(1000).unwrap();
let stats = manager.stats();
assert_eq!(stats.initial_fuel, 1_000_000);
assert_eq!(stats.total_consumed, 5000);
assert_eq!(stats.total_refueled, 1000);
assert_eq!(stats.effective_consumed(), 4000);
}
#[test]
fn test_fuel_cost_estimates() {
let estimates = FuelCostEstimates::default();
assert_eq!(estimates.estimate_instructions(1000), 1000);
assert_eq!(estimates.estimate_memory_pages(10), 10_000);
assert_eq!(estimates.estimate_host_calls(100), 10_000);
}
}