priority_lfu/builder.rs
1use crate::cache::Cache;
2use crate::lifecycle::{DefaultLifecycle, Lifecycle};
3
4/// Builder for configuring a Cache.
5///
6/// # Example
7///
8/// ```
9/// use priority_lfu::CacheBuilder;
10///
11/// let cache = CacheBuilder::new(1024 * 1024 * 512) // 512 MB
12/// .shards(128)
13/// .build();
14/// ```
15///
16/// # With Lifecycle Hooks
17///
18/// ```
19/// use std::any::Any;
20/// use priority_lfu::{CacheBuilder, Lifecycle};
21///
22/// struct MyLifecycle;
23///
24/// impl Lifecycle for MyLifecycle {
25/// fn on_evict(&self, key: &dyn Any) {
26/// println!("Entry evicted!");
27/// }
28/// }
29///
30/// let cache = CacheBuilder::new(1024 * 1024)
31/// .lifecycle(MyLifecycle)
32/// .build();
33/// ```
34///
35/// # Automatic Shard Scaling
36///
37/// By default, the cache uses up to 64 shards, but automatically scales down
38/// for smaller caches to ensure each shard has at least 4KB capacity. This
39/// prevents premature eviction due to uneven hash distribution.
40///
41/// - 256KB+ capacity: 64 shards (4KB+ per shard)
42/// - 64KB capacity: 16 shards (4KB per shard)
43/// - 4KB capacity: 1 shard (4KB per shard)
44///
45/// You can override this with [`shards()`], but the count may still be reduced
46/// if the capacity is too small to support the requested number.
47pub struct CacheBuilder<L: Lifecycle = DefaultLifecycle> {
48 max_size: usize,
49 shard_count: Option<usize>,
50 lifecycle: L,
51}
52
53impl CacheBuilder<DefaultLifecycle> {
54 /// Create a new builder with the given maximum size in bytes.
55 pub fn new(max_size_bytes: usize) -> Self {
56 Self {
57 max_size: max_size_bytes,
58 shard_count: None,
59 lifecycle: DefaultLifecycle,
60 }
61 }
62}
63
64impl<L: Lifecycle> CacheBuilder<L> {
65 /// Set the number of shards.
66 ///
67 /// More shards reduce contention but increase memory overhead.
68 /// Will be rounded up to the next power of 2.
69 ///
70 /// **Note**: The shard count may be reduced if the capacity is too small to
71 /// support the requested number of shards (minimum 4KB per shard). This prevents
72 /// premature eviction due to uneven hash distribution.
73 ///
74 /// Default: up to 64 shards, scaled based on capacity
75 pub fn shards(mut self, count: usize) -> Self {
76 self.shard_count = Some(count);
77 self
78 }
79
80 /// Set the lifecycle hooks.
81 ///
82 /// Lifecycle hooks are called when entries are evicted, removed, or cleared.
83 /// See [`Lifecycle`] for details.
84 ///
85 /// # Example
86 ///
87 /// ```
88 /// use std::any::Any;
89 /// use priority_lfu::{CacheBuilder, Lifecycle};
90 ///
91 /// struct MyLifecycle;
92 ///
93 /// impl Lifecycle for MyLifecycle {
94 /// fn on_evict(&self, _key: &dyn Any) {
95 /// println!("Entry evicted!");
96 /// }
97 /// }
98 ///
99 /// let cache = CacheBuilder::new(1024)
100 /// .lifecycle(MyLifecycle)
101 /// .build();
102 /// ```
103 pub fn lifecycle<L2: Lifecycle>(self, lifecycle: L2) -> CacheBuilder<L2> {
104 CacheBuilder {
105 max_size: self.max_size,
106 shard_count: self.shard_count,
107 lifecycle,
108 }
109 }
110
111 /// Build the cache with the configured settings.
112 pub fn build(self) -> Cache<L> {
113 match self.shard_count {
114 Some(count) => Cache::with_shards_and_lifecycle(self.max_size, count, self.lifecycle),
115 None => Cache::with_lifecycle(self.max_size, self.lifecycle),
116 }
117 }
118}
119
120impl Default for CacheBuilder<DefaultLifecycle> {
121 /// Create a builder with default settings and 1GB capacity.
122 fn default() -> Self {
123 Self::new(1024 * 1024 * 1024) // 1 GB
124 }
125}
126
127#[cfg(test)]
128mod tests {
129 use super::*;
130
131 #[test]
132 fn test_builder_default() {
133 let cache = CacheBuilder::new(1024).build();
134 assert!(cache.is_empty());
135 }
136
137 #[test]
138 fn test_builder_with_shards() {
139 let cache = CacheBuilder::new(1024).shards(32).build();
140 assert!(cache.is_empty());
141 }
142
143 #[test]
144 fn test_builder_full_config() {
145 let cache = CacheBuilder::new(10240).shards(32).build();
146
147 assert!(cache.is_empty());
148 assert_eq!(cache.size(), 0);
149 }
150}