Files
herolib/lib/data/cache/cache.v
2025-01-31 15:39:44 +03:00

168 lines
4.1 KiB
V

module cache
import time
import math
// CacheConfig holds cache configuration parameters
pub struct CacheConfig {
pub mut:
max_entries u32 = 1000 // Maximum number of entries
max_size_mb f64 = 100.0 // Maximum cache size in MB
ttl_seconds i64 = 3600 // Time-to-live in seconds (0 = no TTL)
eviction_ratio f64 = 0.05 // Percentage of entries to evict when full (5%)
}
// CacheEntry represents a cached object with its metadata
@[heap]
struct CacheEntry[T] {
mut:
obj T // Reference to the cached object
last_access i64 // Unix timestamp of last access
created_at i64 // Unix timestamp of creation
size u32 // Approximate size in bytes
}
// Cache manages the in-memory caching of objects
pub struct Cache[T] {
mut:
entries map[u32]&CacheEntry[T] // Map of object ID to cache entry
config CacheConfig // Cache configuration
access_log []u32 // Ordered list of object IDs by access time
total_size u64 // Total size of cached entries in bytes
}
// new_cache creates a new cache instance with the given configuration
pub fn new_cache[T](config CacheConfig) &Cache[T] {
return &Cache[T]{
entries: map[u32]&CacheEntry[T]{}
config: config
access_log: []u32{cap: int(config.max_entries)}
total_size: 0
}
}
// get retrieves an object from the cache if it exists
pub fn (mut c Cache[T]) get(id u32) ?&T {
if entry := c.entries[id] {
now := time.now().unix()
// Check TTL
if c.config.ttl_seconds > 0 {
if (now - entry.created_at) > c.config.ttl_seconds {
c.remove(id)
return none
}
}
// Update access time
unsafe {
entry.last_access = now
}
// Move ID to end of access log
idx := c.access_log.index(id)
if idx >= 0 {
c.access_log.delete(idx)
}
c.access_log << id
return &entry.obj
}
return none
}
// set adds or updates an object in the cache
pub fn (mut c Cache[T]) set(id u32, obj &T) {
now := time.now().unix()
// Calculate entry size (approximate)
entry_size := sizeof(T) + sizeof(CacheEntry[T])
// Check memory and entry count limits
new_total := c.total_size + u64(entry_size)
max_bytes := u64(c.config.max_size_mb * 1024 * 1024)
// Always evict if we're at or above max_entries
if c.entries.len >= int(c.config.max_entries) {
c.evict()
} else if new_total > max_bytes {
// Otherwise evict only if we're over memory limit
c.evict()
}
// Create new entry
entry := &CacheEntry[T]{
obj: *obj
last_access: now
created_at: now
size: u32(entry_size)
}
// Update total size
if old := c.entries[id] {
c.total_size -= u64(old.size)
}
c.total_size += u64(entry_size)
// Add to entries map
c.entries[id] = entry
// Update access log
idx := c.access_log.index(id)
if idx >= 0 {
c.access_log.delete(idx)
}
c.access_log << id
// Ensure access_log stays in sync with entries
if c.access_log.len > c.entries.len {
c.access_log = c.access_log[c.access_log.len - c.entries.len..]
}
}
// evict removes entries based on configured eviction ratio
fn (mut c Cache[T]) evict() {
// If we're at max entries, remove enough to get to 80% capacity
target_size := int(c.config.max_entries) * 8 / 10 // 80%
num_to_evict := if c.entries.len >= int(c.config.max_entries) {
c.entries.len - target_size
} else {
math.max(1, int(c.entries.len * c.config.eviction_ratio))
}
if num_to_evict > 0 {
// Remove oldest entries
mut evicted_size := u64(0)
for i := 0; i < num_to_evict && i < c.access_log.len; i++ {
id := c.access_log[i]
if entry := c.entries[id] {
evicted_size += u64(entry.size)
c.entries.delete(id)
}
}
// Update total size and access log
c.total_size -= evicted_size
c.access_log = c.access_log[num_to_evict..]
}
}
// remove deletes a single entry from the cache
pub fn (mut c Cache[T]) remove(id u32) {
if entry := c.entries[id] {
c.total_size -= u64(entry.size)
}
c.entries.delete(id)
}
// clear empties the cache
pub fn (mut c Cache[T]) clear() {
c.entries.clear()
c.access_log.clear()
c.total_size = 0
}
// len returns the number of entries in the cache
pub fn (c &Cache[T]) len() int {
return c.entries.len
}