use crate::error::TimerError;
use std::num::NonZeroUsize;
use std::time::Duration;
#[derive(Debug, Clone)]
pub struct WheelConfig {
pub l0_tick_duration: Duration,
pub l0_slot_count: usize,
pub l1_tick_duration: Duration,
pub l1_slot_count: usize,
}
impl Default for WheelConfig {
fn default() -> Self {
Self {
l0_tick_duration: Duration::from_millis(10),
l0_slot_count: 512,
l1_tick_duration: Duration::from_secs(1),
l1_slot_count: 64,
}
}
}
impl WheelConfig {
pub fn builder() -> WheelConfigBuilder {
WheelConfigBuilder::default()
}
}
#[derive(Debug, Clone)]
pub struct WheelConfigBuilder {
l0_tick_duration: Duration,
l0_slot_count: usize,
l1_tick_duration: Duration,
l1_slot_count: usize,
}
impl Default for WheelConfigBuilder {
fn default() -> Self {
Self {
l0_tick_duration: Duration::from_millis(10),
l0_slot_count: 512,
l1_tick_duration: Duration::from_secs(1),
l1_slot_count: 64,
}
}
}
impl WheelConfigBuilder {
pub fn l0_tick_duration(mut self, duration: Duration) -> Self {
self.l0_tick_duration = duration;
self
}
pub fn l0_slot_count(mut self, count: usize) -> Self {
self.l0_slot_count = count;
self
}
pub fn l1_tick_duration(mut self, duration: Duration) -> Self {
self.l1_tick_duration = duration;
self
}
pub fn l1_slot_count(mut self, count: usize) -> Self {
self.l1_slot_count = count;
self
}
pub fn build(self) -> Result<WheelConfig, TimerError> {
if self.l0_tick_duration.is_zero() {
return Err(TimerError::InvalidConfiguration {
field: "l0_tick_duration".to_string(),
reason: "L0 layer tick duration must be greater than 0".to_string(),
});
}
if self.l0_slot_count == 0 {
return Err(TimerError::InvalidSlotCount {
slot_count: self.l0_slot_count,
reason: "L0 layer slot count must be greater than 0",
});
}
if !self.l0_slot_count.is_power_of_two() {
return Err(TimerError::InvalidSlotCount {
slot_count: self.l0_slot_count,
reason: "L0 layer slot count must be power of 2",
});
}
if self.l1_tick_duration.is_zero() {
return Err(TimerError::InvalidConfiguration {
field: "l1_tick_duration".to_string(),
reason: "L1 layer tick duration must be greater than 0".to_string(),
});
}
if self.l1_slot_count == 0 {
return Err(TimerError::InvalidSlotCount {
slot_count: self.l1_slot_count,
reason: "L1 layer slot count must be greater than 0",
});
}
if !self.l1_slot_count.is_power_of_two() {
return Err(TimerError::InvalidSlotCount {
slot_count: self.l1_slot_count,
reason: "L1 layer slot count must be power of 2",
});
}
let l0_ms = self.l0_tick_duration.as_millis() as u64;
let l1_ms = self.l1_tick_duration.as_millis() as u64;
if !l1_ms.is_multiple_of(l0_ms) {
return Err(TimerError::InvalidConfiguration {
field: "l1_tick_duration".to_string(),
reason: format!(
"L1 tick duration ({} ms) must be an integer multiple of L0 tick duration ({} ms)",
l1_ms, l0_ms
),
});
}
Ok(WheelConfig {
l0_tick_duration: self.l0_tick_duration,
l0_slot_count: self.l0_slot_count,
l1_tick_duration: self.l1_tick_duration,
l1_slot_count: self.l1_slot_count,
})
}
}
#[derive(Debug, Clone)]
pub struct ServiceConfig {
pub command_channel_capacity: NonZeroUsize,
pub timeout_channel_capacity: NonZeroUsize,
}
impl Default for ServiceConfig {
fn default() -> Self {
Self {
command_channel_capacity: NonZeroUsize::new(512).unwrap(),
timeout_channel_capacity: NonZeroUsize::new(1000).unwrap(),
}
}
}
impl ServiceConfig {
pub fn builder() -> ServiceConfigBuilder {
ServiceConfigBuilder::default()
}
}
#[derive(Debug, Clone)]
pub struct ServiceConfigBuilder {
pub command_channel_capacity: NonZeroUsize,
pub timeout_channel_capacity: NonZeroUsize,
}
impl Default for ServiceConfigBuilder {
fn default() -> Self {
let config = ServiceConfig::default();
Self {
command_channel_capacity: config.command_channel_capacity,
timeout_channel_capacity: config.timeout_channel_capacity,
}
}
}
impl ServiceConfigBuilder {
pub fn command_channel_capacity(mut self, capacity: NonZeroUsize) -> Self {
self.command_channel_capacity = capacity;
self
}
pub fn timeout_channel_capacity(mut self, capacity: NonZeroUsize) -> Self {
self.timeout_channel_capacity = capacity;
self
}
pub fn build(self) -> ServiceConfig {
ServiceConfig {
command_channel_capacity: self.command_channel_capacity,
timeout_channel_capacity: self.timeout_channel_capacity,
}
}
}
#[derive(Debug, Clone)]
pub struct BatchConfig {
pub small_batch_threshold: usize,
}
impl Default for BatchConfig {
fn default() -> Self {
Self {
small_batch_threshold: 10,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct TimerConfig {
pub wheel: WheelConfig,
pub service: ServiceConfig,
pub batch: BatchConfig,
}
impl TimerConfig {
pub fn builder() -> TimerConfigBuilder {
TimerConfigBuilder::default()
}
}
#[derive(Debug, Default)]
pub struct TimerConfigBuilder {
wheel_builder: WheelConfigBuilder,
service_builder: ServiceConfigBuilder,
batch_config: BatchConfig,
}
impl TimerConfigBuilder {
pub fn command_channel_capacity(mut self, capacity: NonZeroUsize) -> Self {
self.service_builder = self.service_builder.command_channel_capacity(capacity);
self
}
pub fn timeout_channel_capacity(mut self, capacity: NonZeroUsize) -> Self {
self.service_builder = self.service_builder.timeout_channel_capacity(capacity);
self
}
pub fn small_batch_threshold(mut self, threshold: usize) -> Self {
self.batch_config.small_batch_threshold = threshold;
self
}
pub fn build(self) -> Result<TimerConfig, TimerError> {
Ok(TimerConfig {
wheel: self.wheel_builder.build()?,
service: self.service_builder.build(),
batch: self.batch_config,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_wheel_config_default() {
let config = WheelConfig::default();
assert_eq!(config.l0_tick_duration, Duration::from_millis(10));
assert_eq!(config.l0_slot_count, 512);
assert_eq!(config.l1_tick_duration, Duration::from_secs(1));
assert_eq!(config.l1_slot_count, 64);
}
#[test]
fn test_wheel_config_builder() {
let config = WheelConfig::builder()
.l0_tick_duration(Duration::from_millis(20))
.l0_slot_count(1024)
.l1_tick_duration(Duration::from_secs(2))
.l1_slot_count(128)
.build()
.unwrap();
assert_eq!(config.l0_tick_duration, Duration::from_millis(20));
assert_eq!(config.l0_slot_count, 1024);
assert_eq!(config.l1_tick_duration, Duration::from_secs(2));
assert_eq!(config.l1_slot_count, 128);
}
#[test]
fn test_wheel_config_validation_zero_tick() {
let result = WheelConfig::builder()
.l0_tick_duration(Duration::ZERO)
.build();
assert!(result.is_err());
}
#[test]
fn test_wheel_config_validation_invalid_slot_count() {
let result = WheelConfig::builder().l0_slot_count(100).build();
assert!(result.is_err());
}
#[test]
fn test_service_config_builder() {
let config = ServiceConfig::builder()
.command_channel_capacity(NonZeroUsize::new(1024).unwrap())
.timeout_channel_capacity(NonZeroUsize::new(2000).unwrap())
.build();
assert_eq!(
config.command_channel_capacity,
NonZeroUsize::new(1024).unwrap()
);
assert_eq!(
config.timeout_channel_capacity,
NonZeroUsize::new(2000).unwrap()
);
}
#[test]
fn test_batch_config_default() {
let config = BatchConfig::default();
assert_eq!(config.small_batch_threshold, 10);
}
#[test]
fn test_timer_config_default() {
let config = TimerConfig::default();
assert_eq!(config.wheel.l0_slot_count, 512);
assert_eq!(
config.service.command_channel_capacity,
NonZeroUsize::new(512).unwrap()
);
assert_eq!(config.batch.small_batch_threshold, 10);
}
#[test]
fn test_timer_config_builder() {
let config = TimerConfig::builder()
.command_channel_capacity(NonZeroUsize::new(1024).unwrap())
.timeout_channel_capacity(NonZeroUsize::new(2000).unwrap())
.small_batch_threshold(20)
.build()
.unwrap();
assert_eq!(
config.service.command_channel_capacity,
NonZeroUsize::new(1024).unwrap()
);
assert_eq!(
config.service.timeout_channel_capacity,
NonZeroUsize::new(2000).unwrap()
);
assert_eq!(config.batch.small_batch_threshold, 20);
}
}