use crate::error::TimerError;
use std::time::Duration;
#[derive(Debug, Clone)]
pub struct HierarchicalWheelConfig {
pub l0_tick_duration: Duration,
pub l0_slot_count: usize,
pub l1_tick_duration: Duration,
pub l1_slot_count: usize,
}
impl Default for HierarchicalWheelConfig {
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,
}
}
}
#[derive(Debug, Clone)]
pub struct WheelConfig {
pub hierarchical: HierarchicalWheelConfig,
}
impl Default for WheelConfig {
fn default() -> Self {
Self {
hierarchical: HierarchicalWheelConfig::default(),
}
}
}
impl WheelConfig {
pub fn builder() -> WheelConfigBuilder {
WheelConfigBuilder::default()
}
}
#[derive(Debug, Clone)]
pub struct WheelConfigBuilder {
hierarchical: HierarchicalWheelConfig,
}
impl Default for WheelConfigBuilder {
fn default() -> Self {
Self {
hierarchical: HierarchicalWheelConfig::default(),
}
}
}
impl WheelConfigBuilder {
pub fn l0_tick_duration(mut self, duration: Duration) -> Self {
self.hierarchical.l0_tick_duration = duration;
self
}
pub fn l0_slot_count(mut self, count: usize) -> Self {
self.hierarchical.l0_slot_count = count;
self
}
pub fn l1_tick_duration(mut self, duration: Duration) -> Self {
self.hierarchical.l1_tick_duration = duration;
self
}
pub fn l1_slot_count(mut self, count: usize) -> Self {
self.hierarchical.l1_slot_count = count;
self
}
pub fn build(self) -> Result<WheelConfig, TimerError> {
let h = &self.hierarchical;
if h.l0_tick_duration.is_zero() {
return Err(TimerError::InvalidConfiguration {
field: "l0_tick_duration".to_string(),
reason: "L0 层 tick 时长必须大于 0".to_string(),
});
}
if h.l0_slot_count == 0 {
return Err(TimerError::InvalidSlotCount {
slot_count: h.l0_slot_count,
reason: "L0 层槽位数量必须大于 0",
});
}
if !h.l0_slot_count.is_power_of_two() {
return Err(TimerError::InvalidSlotCount {
slot_count: h.l0_slot_count,
reason: "L0 层槽位数量必须是 2 的幂次方",
});
}
if h.l1_tick_duration.is_zero() {
return Err(TimerError::InvalidConfiguration {
field: "l1_tick_duration".to_string(),
reason: "L1 层 tick 时长必须大于 0".to_string(),
});
}
if h.l1_slot_count == 0 {
return Err(TimerError::InvalidSlotCount {
slot_count: h.l1_slot_count,
reason: "L1 层槽位数量必须大于 0",
});
}
if !h.l1_slot_count.is_power_of_two() {
return Err(TimerError::InvalidSlotCount {
slot_count: h.l1_slot_count,
reason: "L1 层槽位数量必须是 2 的幂次方",
});
}
let l0_ms = h.l0_tick_duration.as_millis() as u64;
let l1_ms = h.l1_tick_duration.as_millis() as u64;
if l1_ms % l0_ms != 0 {
return Err(TimerError::InvalidConfiguration {
field: "l1_tick_duration".to_string(),
reason: format!(
"L1 tick 时长 ({} ms) 必须是 L0 tick 时长 ({} ms) 的整数倍",
l1_ms, l0_ms
),
});
}
Ok(WheelConfig {
hierarchical: self.hierarchical,
})
}
}
#[derive(Debug, Clone)]
pub struct ServiceConfig {
pub command_channel_capacity: usize,
pub timeout_channel_capacity: usize,
}
impl Default for ServiceConfig {
fn default() -> Self {
Self {
command_channel_capacity: 512,
timeout_channel_capacity: 1000,
}
}
}
impl ServiceConfig {
pub fn builder() -> ServiceConfigBuilder {
ServiceConfigBuilder::default()
}
}
#[derive(Debug, Clone)]
pub struct ServiceConfigBuilder {
command_channel_capacity: usize,
timeout_channel_capacity: usize,
}
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: usize) -> Self {
self.command_channel_capacity = capacity;
self
}
pub fn timeout_channel_capacity(mut self, capacity: usize) -> Self {
self.timeout_channel_capacity = capacity;
self
}
pub fn build(self) -> Result<ServiceConfig, TimerError> {
if self.command_channel_capacity == 0 {
return Err(TimerError::InvalidConfiguration {
field: "command_channel_capacity".to_string(),
reason: "命令通道容量必须大于 0".to_string(),
});
}
if self.timeout_channel_capacity == 0 {
return Err(TimerError::InvalidConfiguration {
field: "timeout_channel_capacity".to_string(),
reason: "超时通道容量必须大于 0".to_string(),
});
}
Ok(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)]
pub struct TimerConfig {
pub wheel: WheelConfig,
pub service: ServiceConfig,
pub batch: BatchConfig,
}
impl Default for TimerConfig {
fn default() -> Self {
Self {
wheel: WheelConfig::default(),
service: ServiceConfig::default(),
batch: BatchConfig::default(),
}
}
}
impl TimerConfig {
pub fn builder() -> TimerConfigBuilder {
TimerConfigBuilder::default()
}
}
#[derive(Debug)]
pub struct TimerConfigBuilder {
wheel_builder: WheelConfigBuilder,
service_builder: ServiceConfigBuilder,
batch_config: BatchConfig,
}
impl Default for TimerConfigBuilder {
fn default() -> Self {
Self {
wheel_builder: WheelConfigBuilder::default(),
service_builder: ServiceConfigBuilder::default(),
batch_config: BatchConfig::default(),
}
}
}
impl TimerConfigBuilder {
pub fn command_channel_capacity(mut self, capacity: usize) -> Self {
self.service_builder = self.service_builder.command_channel_capacity(capacity);
self
}
pub fn timeout_channel_capacity(mut self, capacity: usize) -> 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.hierarchical.l0_tick_duration, Duration::from_millis(10));
assert_eq!(config.hierarchical.l0_slot_count, 512);
assert_eq!(config.hierarchical.l1_tick_duration, Duration::from_secs(1));
assert_eq!(config.hierarchical.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.hierarchical.l0_tick_duration, Duration::from_millis(20));
assert_eq!(config.hierarchical.l0_slot_count, 1024);
assert_eq!(config.hierarchical.l1_tick_duration, Duration::from_secs(2));
assert_eq!(config.hierarchical.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_default() {
let config = ServiceConfig::default();
assert_eq!(config.command_channel_capacity, 512);
assert_eq!(config.timeout_channel_capacity, 1000);
}
#[test]
fn test_service_config_builder() {
let config = ServiceConfig::builder()
.command_channel_capacity(1024)
.timeout_channel_capacity(2000)
.build()
.unwrap();
assert_eq!(config.command_channel_capacity, 1024);
assert_eq!(config.timeout_channel_capacity, 2000);
}
#[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.hierarchical.l0_slot_count, 512);
assert_eq!(config.service.command_channel_capacity, 512);
assert_eq!(config.batch.small_batch_threshold, 10);
}
#[test]
fn test_timer_config_builder() {
let config = TimerConfig::builder()
.command_channel_capacity(1024)
.timeout_channel_capacity(2000)
.small_batch_threshold(20)
.build()
.unwrap();
assert_eq!(config.service.command_channel_capacity, 1024);
assert_eq!(config.service.timeout_channel_capacity, 2000);
assert_eq!(config.batch.small_batch_threshold, 20);
}
}