package sync
import (
"sync"
"sync/atomic"
"time"
)
type Cache struct {
capacity, length uint64
entries sync.Map
pool sync.Pool
}
type CacheEntry struct {
V any
expiration time.Time
}
func NewCache(capacity int) Cache {
return Cache{
capacity: uint64(capacity),
pool: sync.Pool{New: func() any {
return new(CacheEntry)
}},
}
}
func (c *Cache) Load(key string) *CacheEntry {
if mapEntry, ok := c.entries.Load(key); ok {
entry := mapEntry.(*CacheEntry)
if c.expired(entry) {
c.entries.Delete(key)
return nil
}
return entry
}
return nil
}
func (c *Cache) Store(key string, val any, expiration time.Time) {
if c.length > c.capacity {
c.evict()
}
poolEntry := c.pool.Get()
if mapEntry, loaded := c.entries.LoadOrStore(key, poolEntry); loaded {
entry := mapEntry.(*CacheEntry)
entry.V = val
entry.expiration = expiration
c.pool.Put(poolEntry)
} else {
entry := poolEntry.(*CacheEntry)
entry.V = val
entry.expiration = expiration
atomic.AddUint64(&c.length, 1)
}
}
func (c *Cache) Delete(key string) bool {
_, loaded := c.entries.LoadAndDelete(key)
if loaded {
atomic.AddUint64(&c.length, ^uint64(0))
}
return loaded
}
func (c *Cache) evict() {
c.entries.Range(func(key, mapEntry any) bool {
entry := mapEntry.(*CacheEntry)
if c.expired(entry) {
c.Delete(key.(string))
}
return true
})
}
func (c *Cache) expired(e *CacheEntry) bool {
return e.expiration.Before(time.Now())
}