use std::{num::NonZeroU32, prelude::v1::*, time::Duration};
use super::nanos::Nanos;
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(
feature = "python",
pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.network", from_py_object)
)]
#[cfg_attr(
feature = "python",
pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.network")
)]
pub struct Quota {
pub(crate) max_burst: NonZeroU32,
pub(crate) replenish_1_per: Duration,
}
impl Quota {
#[must_use]
pub const fn per_second(max_burst: NonZeroU32) -> Option<Self> {
let replenish_interval_ns = Duration::from_secs(1).as_nanos() / (max_burst.get() as u128);
if replenish_interval_ns == 0 {
return None;
}
Some(Self {
max_burst,
replenish_1_per: Duration::from_nanos(replenish_interval_ns as u64),
})
}
#[must_use]
pub const fn per_minute(max_burst: NonZeroU32) -> Self {
let replenish_interval_ns = Duration::from_secs(60).as_nanos() / (max_burst.get() as u128);
Self {
max_burst,
replenish_1_per: Duration::from_nanos(replenish_interval_ns as u64),
}
}
#[must_use]
pub const fn per_hour(max_burst: NonZeroU32) -> Self {
let replenish_interval_ns = Duration::from_hours(1).as_nanos() / (max_burst.get() as u128);
Self {
max_burst,
replenish_1_per: Duration::from_nanos(replenish_interval_ns as u64),
}
}
#[must_use]
pub const fn with_period(replenish_1_per: Duration) -> Option<Self> {
if replenish_1_per.as_nanos() == 0 {
None
} else {
#[allow(clippy::missing_panics_doc)]
Some(Self {
max_burst: NonZeroU32::new(1).unwrap(),
replenish_1_per,
})
}
}
#[must_use]
pub const fn allow_burst(self, max_burst: NonZeroU32) -> Self {
Self { max_burst, ..self }
}
#[deprecated(
since = "0.2.0",
note = "This constructor is often confusing and non-intuitive. \
Use the `per_(interval)` / `with_period` and `max_burst` constructors instead."
)]
#[must_use]
pub fn new(max_burst: NonZeroU32, replenish_all_per: Duration) -> Option<Self> {
if replenish_all_per.as_nanos() == 0 {
None
} else {
Some(Self {
max_burst,
replenish_1_per: replenish_all_per / max_burst.get(),
})
}
}
}
impl Quota {
#[must_use]
pub const fn replenish_interval(&self) -> Duration {
self.replenish_1_per
}
#[must_use]
pub const fn burst_size(&self) -> NonZeroU32 {
self.max_burst
}
#[must_use]
pub const fn burst_size_replenished_in(&self) -> Duration {
let fill_in_ns = self.replenish_1_per.as_nanos() * self.max_burst.get() as u128;
Duration::from_nanos(fill_in_ns as u64)
}
}
impl Quota {
pub(crate) fn from_gcra_parameters(t: Nanos, tau: Nanos) -> Self {
let t_u64 = t.as_u64();
let tau_u64 = tau.as_u64();
assert!(t_u64 != 0, "Invalid GCRA parameter: t cannot be zero");
let division_result = tau_u64 / t_u64;
assert!(
division_result != 0,
"Invalid GCRA parameters: tau/t results in zero burst capacity"
);
assert!(
u32::try_from(division_result).is_ok(),
"Invalid GCRA parameters: tau/t exceeds u32::MAX"
);
let max_burst = NonZeroU32::new(division_result as u32)
.expect("Division result should be non-zero after validation");
let replenish_1_per = t.into();
Self {
max_burst,
replenish_1_per,
}
}
}