1use crate::error::TimerError;
7use std::num::NonZeroUsize;
8use std::time::Duration;
9
10#[derive(Debug, Clone)]
36pub struct WheelConfig {
37 l0_tick_duration: Duration,
41
42 l0_slot_count: usize,
46
47 l1_tick_duration: Duration,
51
52 l1_slot_count: usize,
56}
57
58impl Default for WheelConfig {
59 fn default() -> Self {
60 Self {
61 l0_tick_duration: Duration::from_millis(10),
62 l0_slot_count: 512,
63 l1_tick_duration: Duration::from_secs(1),
64 l1_slot_count: 64,
65 }
66 }
67}
68
69impl WheelConfig {
70 pub fn builder() -> WheelConfigBuilder {
72 WheelConfigBuilder::default()
73 }
74
75 pub fn l0_tick_duration(&self) -> Duration {
77 self.l0_tick_duration
78 }
79
80 pub fn l0_slot_count(&self) -> usize {
82 self.l0_slot_count
83 }
84
85 pub fn l1_tick_duration(&self) -> Duration {
87 self.l1_tick_duration
88 }
89
90 pub fn l1_slot_count(&self) -> usize {
92 self.l1_slot_count
93 }
94
95 pub(crate) fn validate(self) -> Result<Self, TimerError> {
96 WheelConfigBuilder {
97 l0_tick_duration: self.l0_tick_duration,
98 l0_slot_count: self.l0_slot_count,
99 l1_tick_duration: self.l1_tick_duration,
100 l1_slot_count: self.l1_slot_count,
101 }
102 .build()
103 }
104}
105
106#[derive(Debug, Clone)]
108pub struct WheelConfigBuilder {
109 l0_tick_duration: Duration,
110 l0_slot_count: usize,
111 l1_tick_duration: Duration,
112 l1_slot_count: usize,
113}
114
115impl Default for WheelConfigBuilder {
116 fn default() -> Self {
117 Self {
118 l0_tick_duration: Duration::from_millis(10),
119 l0_slot_count: 512,
120 l1_tick_duration: Duration::from_secs(1),
121 l1_slot_count: 64,
122 }
123 }
124}
125
126impl WheelConfigBuilder {
127 pub fn l0_tick_duration(mut self, duration: Duration) -> Self {
129 self.l0_tick_duration = duration;
130 self
131 }
132
133 pub fn l0_slot_count(mut self, count: usize) -> Self {
135 self.l0_slot_count = count;
136 self
137 }
138
139 pub fn l1_tick_duration(mut self, duration: Duration) -> Self {
141 self.l1_tick_duration = duration;
142 self
143 }
144
145 pub fn l1_slot_count(mut self, count: usize) -> Self {
147 self.l1_slot_count = count;
148 self
149 }
150
151 pub fn build(self) -> Result<WheelConfig, TimerError> {
164 if self.l0_tick_duration.is_zero() {
166 return Err(TimerError::InvalidConfiguration {
167 field: "l0_tick_duration".to_string(),
168 reason: "L0 layer tick duration must be greater than 0".to_string(),
169 });
170 }
171
172 if self.l0_slot_count == 0 {
173 return Err(TimerError::InvalidSlotCount {
174 slot_count: self.l0_slot_count,
175 reason: "L0 layer slot count must be greater than 0",
176 });
177 }
178
179 if !self.l0_slot_count.is_power_of_two() {
180 return Err(TimerError::InvalidSlotCount {
181 slot_count: self.l0_slot_count,
182 reason: "L0 layer slot count must be power of 2",
183 });
184 }
185
186 if self.l1_tick_duration.is_zero() {
188 return Err(TimerError::InvalidConfiguration {
189 field: "l1_tick_duration".to_string(),
190 reason: "L1 layer tick duration must be greater than 0".to_string(),
191 });
192 }
193
194 if self.l1_slot_count == 0 {
195 return Err(TimerError::InvalidSlotCount {
196 slot_count: self.l1_slot_count,
197 reason: "L1 layer slot count must be greater than 0",
198 });
199 }
200
201 if !self.l1_slot_count.is_power_of_two() {
202 return Err(TimerError::InvalidSlotCount {
203 slot_count: self.l1_slot_count,
204 reason: "L1 layer slot count must be power of 2",
205 });
206 }
207
208 let l0_nanos = self.l0_tick_duration.as_nanos();
211 let l1_nanos = self.l1_tick_duration.as_nanos();
212 if !l1_nanos.is_multiple_of(l0_nanos) {
213 return Err(TimerError::InvalidConfiguration {
214 field: "l1_tick_duration".to_string(),
215 reason: format!(
216 "L1 tick duration ({:?}) must be an integer multiple of L0 tick duration ({:?})",
217 self.l1_tick_duration, self.l0_tick_duration
218 ),
219 });
220 }
221
222 if u64::try_from(l1_nanos / l0_nanos).is_err() {
223 return Err(TimerError::InvalidConfiguration {
224 field: "l1_tick_duration".to_string(),
225 reason: "L1/L0 tick ratio must fit in u64".to_string(),
226 });
227 }
228
229 Ok(WheelConfig {
230 l0_tick_duration: self.l0_tick_duration,
231 l0_slot_count: self.l0_slot_count,
232 l1_tick_duration: self.l1_tick_duration,
233 l1_slot_count: self.l1_slot_count,
234 })
235 }
236}
237
238#[derive(Debug, Clone)]
261pub struct ServiceConfig {
262 pub command_channel_capacity: NonZeroUsize,
266
267 pub timeout_channel_capacity: NonZeroUsize,
271}
272
273impl Default for ServiceConfig {
274 fn default() -> Self {
275 Self {
276 command_channel_capacity: NonZeroUsize::new(512).unwrap(),
277 timeout_channel_capacity: NonZeroUsize::new(1000).unwrap(),
278 }
279 }
280}
281
282impl ServiceConfig {
283 pub fn builder() -> ServiceConfigBuilder {
285 ServiceConfigBuilder::default()
286 }
287}
288
289#[derive(Debug, Clone)]
293pub struct ServiceConfigBuilder {
294 pub command_channel_capacity: NonZeroUsize,
298
299 pub timeout_channel_capacity: NonZeroUsize,
303}
304
305impl Default for ServiceConfigBuilder {
306 fn default() -> Self {
307 let config = ServiceConfig::default();
308 Self {
309 command_channel_capacity: config.command_channel_capacity,
310 timeout_channel_capacity: config.timeout_channel_capacity,
311 }
312 }
313}
314
315impl ServiceConfigBuilder {
316 pub fn command_channel_capacity(mut self, capacity: NonZeroUsize) -> Self {
318 self.command_channel_capacity = capacity;
319 self
320 }
321
322 pub fn timeout_channel_capacity(mut self, capacity: NonZeroUsize) -> Self {
324 self.timeout_channel_capacity = capacity;
325 self
326 }
327
328 pub fn build(self) -> ServiceConfig {
347 ServiceConfig {
348 command_channel_capacity: self.command_channel_capacity,
349 timeout_channel_capacity: self.timeout_channel_capacity,
350 }
351 }
352}
353
354#[derive(Debug, Clone)]
377pub struct BatchConfig {
378 pub small_batch_threshold: usize,
385}
386
387impl Default for BatchConfig {
388 fn default() -> Self {
389 Self {
390 small_batch_threshold: 10,
391 }
392 }
393}
394
395#[derive(Debug, Clone, Default)]
419pub struct TimerConfig {
420 pub wheel: WheelConfig,
422 pub service: ServiceConfig,
424 pub batch: BatchConfig,
426}
427
428impl TimerConfig {
429 pub fn builder() -> TimerConfigBuilder {
431 TimerConfigBuilder::default()
432 }
433}
434
435#[derive(Debug, Default)]
439pub struct TimerConfigBuilder {
440 wheel_builder: WheelConfigBuilder,
441 service_builder: ServiceConfigBuilder,
442 batch_config: BatchConfig,
443}
444
445impl TimerConfigBuilder {
446 pub fn command_channel_capacity(mut self, capacity: NonZeroUsize) -> Self {
448 self.service_builder = self.service_builder.command_channel_capacity(capacity);
449 self
450 }
451
452 pub fn timeout_channel_capacity(mut self, capacity: NonZeroUsize) -> Self {
454 self.service_builder = self.service_builder.timeout_channel_capacity(capacity);
455 self
456 }
457
458 pub fn small_batch_threshold(mut self, threshold: usize) -> Self {
460 self.batch_config.small_batch_threshold = threshold;
461 self
462 }
463
464 pub fn build(self) -> Result<TimerConfig, TimerError> {
477 Ok(TimerConfig {
478 wheel: self.wheel_builder.build()?,
479 service: self.service_builder.build(),
480 batch: self.batch_config,
481 })
482 }
483}
484
485#[cfg(test)]
486mod tests {
487 use super::*;
488
489 #[test]
490 fn test_wheel_config_default() {
491 let config = WheelConfig::default();
492 assert_eq!(config.l0_tick_duration(), Duration::from_millis(10));
493 assert_eq!(config.l0_slot_count(), 512);
494 assert_eq!(config.l1_tick_duration(), Duration::from_secs(1));
495 assert_eq!(config.l1_slot_count(), 64);
496 }
497
498 #[test]
499 fn test_wheel_config_builder() {
500 let config = WheelConfig::builder()
501 .l0_tick_duration(Duration::from_millis(20))
502 .l0_slot_count(1024)
503 .l1_tick_duration(Duration::from_secs(2))
504 .l1_slot_count(128)
505 .build()
506 .unwrap();
507
508 assert_eq!(config.l0_tick_duration(), Duration::from_millis(20));
509 assert_eq!(config.l0_slot_count(), 1024);
510 assert_eq!(config.l1_tick_duration(), Duration::from_secs(2));
511 assert_eq!(config.l1_slot_count(), 128);
512 }
513
514 #[test]
515 fn test_wheel_config_validation_zero_tick() {
516 let result = WheelConfig::builder()
517 .l0_tick_duration(Duration::ZERO)
518 .build();
519
520 assert!(result.is_err());
521 }
522
523 #[test]
524 fn test_wheel_config_accepts_sub_millisecond_ticks() {
525 let result = WheelConfig::builder()
526 .l0_tick_duration(Duration::from_micros(500))
527 .l1_tick_duration(Duration::from_millis(1))
528 .build();
529
530 assert!(result.is_ok());
531 }
532
533 #[test]
534 fn test_wheel_config_rejects_tick_ratio_overflow() {
535 let result = WheelConfig::builder()
536 .l0_tick_duration(Duration::from_nanos(1))
537 .l1_tick_duration(Duration::MAX)
538 .build();
539
540 assert!(matches!(
541 result,
542 Err(TimerError::InvalidConfiguration { field, .. })
543 if field == "l1_tick_duration"
544 ));
545 }
546
547 #[test]
548 fn test_wheel_config_validation_invalid_slot_count() {
549 let result = WheelConfig::builder().l0_slot_count(100).build();
550
551 assert!(result.is_err());
552 }
553
554 #[test]
555 fn test_service_config_builder() {
556 let config = ServiceConfig::builder()
557 .command_channel_capacity(NonZeroUsize::new(1024).unwrap())
558 .timeout_channel_capacity(NonZeroUsize::new(2000).unwrap())
559 .build();
560
561 assert_eq!(
562 config.command_channel_capacity,
563 NonZeroUsize::new(1024).unwrap()
564 );
565 assert_eq!(
566 config.timeout_channel_capacity,
567 NonZeroUsize::new(2000).unwrap()
568 );
569 }
570
571 #[test]
572 fn test_batch_config_default() {
573 let config = BatchConfig::default();
574 assert_eq!(config.small_batch_threshold, 10);
575 }
576
577 #[test]
578 fn test_timer_config_default() {
579 let config = TimerConfig::default();
580 assert_eq!(config.wheel.l0_slot_count(), 512);
581 assert_eq!(
582 config.service.command_channel_capacity,
583 NonZeroUsize::new(512).unwrap()
584 );
585 assert_eq!(config.batch.small_batch_threshold, 10);
586 }
587
588 #[test]
589 fn test_timer_config_builder() {
590 let config = TimerConfig::builder()
591 .command_channel_capacity(NonZeroUsize::new(1024).unwrap())
592 .timeout_channel_capacity(NonZeroUsize::new(2000).unwrap())
593 .small_batch_threshold(20)
594 .build()
595 .unwrap();
596
597 assert_eq!(
598 config.service.command_channel_capacity,
599 NonZeroUsize::new(1024).unwrap()
600 );
601 assert_eq!(
602 config.service.timeout_channel_capacity,
603 NonZeroUsize::new(2000).unwrap()
604 );
605 assert_eq!(config.batch.small_batch_threshold, 20);
606 }
607}