zc2 0.0.27

P2P compute broker with credit-based billing, WAL, and broker mesh support
//! Broker-level advertised price `P`, in credits/hour.
//!
//! Thread-safe, lock-free storage so the price can be shared into the tick
//! loop and request handlers and mutated live via `zc price <value>` / the
//! local-only `POST /price` control endpoint.

use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;

use super::worker::default_price_per_hour;

/// A broker's advertised price (credits/hour), shared and mutable across
/// threads via `Arc<AtomicU64>` storing the `f64` bit pattern.
#[derive(Clone)]
pub struct BrokerPrice(Arc<AtomicU64>);

impl BrokerPrice {
    /// Create a new `BrokerPrice` initialized to `initial`.
    pub fn new(initial: f64) -> Self {
        Self(Arc::new(AtomicU64::new(initial.to_bits())))
    }

    /// Current price, in credits/hour.
    pub fn get(&self) -> f64 {
        f64::from_bits(self.0.load(Ordering::Relaxed))
    }

    /// Set the price. Non-finite values (NaN/inf) are ignored (no-op);
    /// negative values are clamped to `0.0`.
    pub fn set(&self, price: f64) {
        if !price.is_finite() {
            return;
        }
        let clamped = price.max(0.0);
        self.0.store(clamped.to_bits(), Ordering::Relaxed);
    }

    /// Build from `ZAKURO_BROKER_PRICE` (parsed as a finite, non-negative
    /// `f64`), falling back to `default_price_per_hour()` (3.6) when the env
    /// var is unset, unparsable, non-finite, or negative.
    pub fn from_env_or_default() -> Self {
        let price = std::env::var("ZAKURO_BROKER_PRICE")
            .ok()
            .and_then(|v| v.parse::<f64>().ok())
            .filter(|v| v.is_finite() && *v >= 0.0)
            .unwrap_or_else(default_price_per_hour);
        Self::new(price)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn broker_price_set_get_and_rejects_bad() {
        let p = BrokerPrice::new(3.6);
        assert_eq!(p.get(), 3.6);
        p.set(5.0);
        assert_eq!(p.get(), 5.0);
        p.set(f64::NAN); // ignored
        assert_eq!(p.get(), 5.0);
        p.set(-1.0); // clamped to 0
        assert_eq!(p.get(), 0.0);
    }

    #[test]
    fn broker_price_from_env_default() {
        // no env var set in this test process -> default 3.6
        let p = BrokerPrice::from_env_or_default();
        assert!(p.get() > 0.0);
    }
}