2020-09-08 15:58:35 -04:00
|
|
|
package d2cache
|
2019-12-16 22:23:01 -05:00
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
2019-12-26 11:13:05 -05:00
|
|
|
"log"
|
2019-12-16 22:23:01 -05:00
|
|
|
"sync"
|
2020-07-08 09:16:56 -04:00
|
|
|
|
|
|
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
2019-12-16 22:23:01 -05:00
|
|
|
)
|
|
|
|
|
2020-07-13 09:05:04 -04:00
|
|
|
var _ d2interface.Cache = &Cache{} // Static check to confirm struct conforms to interface
|
|
|
|
|
2019-12-16 22:23:01 -05:00
|
|
|
type cacheNode struct {
|
|
|
|
next *cacheNode
|
|
|
|
prev *cacheNode
|
|
|
|
key string
|
|
|
|
value interface{}
|
|
|
|
weight int
|
|
|
|
}
|
|
|
|
|
2020-07-03 21:23:44 -04:00
|
|
|
// Cache stores arbitrary data for fast retrieval
|
2020-01-31 23:18:11 -05:00
|
|
|
type Cache struct {
|
2019-12-26 11:13:05 -05:00
|
|
|
head *cacheNode
|
|
|
|
tail *cacheNode
|
|
|
|
lookup map[string]*cacheNode
|
|
|
|
weight int
|
|
|
|
budget int
|
|
|
|
verbose bool
|
|
|
|
mutex sync.Mutex
|
2019-12-16 22:23:01 -05:00
|
|
|
}
|
|
|
|
|
2020-07-03 21:23:44 -04:00
|
|
|
// CreateCache creates an instance of a Cache
|
|
|
|
func CreateCache(budget int) d2interface.Cache {
|
2020-01-31 23:18:11 -05:00
|
|
|
return &Cache{lookup: make(map[string]*cacheNode), budget: budget}
|
|
|
|
}
|
2020-02-01 18:55:56 -05:00
|
|
|
|
2020-07-03 21:23:44 -04:00
|
|
|
// SetVerbose turns on verbose printing (warnings and stuff)
|
2020-02-01 18:55:56 -05:00
|
|
|
func (c *Cache) SetVerbose(verbose bool) {
|
2020-01-31 23:18:11 -05:00
|
|
|
c.verbose = verbose
|
|
|
|
}
|
|
|
|
|
2020-07-03 21:23:44 -04:00
|
|
|
// GetWeight gets the "weight" of a cache
|
2020-01-31 23:18:11 -05:00
|
|
|
func (c *Cache) GetWeight() int {
|
|
|
|
return c.weight
|
|
|
|
}
|
|
|
|
|
2020-07-03 21:23:44 -04:00
|
|
|
// GetBudget gets the memory budget of a cache
|
2020-01-31 23:18:11 -05:00
|
|
|
func (c *Cache) GetBudget() int {
|
|
|
|
return c.budget
|
2019-12-16 22:23:01 -05:00
|
|
|
}
|
|
|
|
|
2020-07-03 21:23:44 -04:00
|
|
|
// Insert inserts an object into the cache
|
2020-01-31 23:18:11 -05:00
|
|
|
func (c *Cache) Insert(key string, value interface{}, weight int) error {
|
2019-12-16 22:23:01 -05:00
|
|
|
c.mutex.Lock()
|
|
|
|
defer c.mutex.Unlock()
|
|
|
|
|
|
|
|
if _, found := c.lookup[key]; found {
|
2020-01-31 23:18:11 -05:00
|
|
|
return errors.New("key already exists in Cache")
|
2019-12-16 22:23:01 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
node := &cacheNode{
|
|
|
|
key: key,
|
|
|
|
value: value,
|
|
|
|
weight: weight,
|
|
|
|
next: c.head,
|
|
|
|
}
|
|
|
|
|
|
|
|
if c.head != nil {
|
|
|
|
c.head.prev = node
|
|
|
|
}
|
|
|
|
|
|
|
|
c.head = node
|
|
|
|
if c.tail == nil {
|
|
|
|
c.tail = node
|
|
|
|
}
|
|
|
|
|
|
|
|
c.lookup[key] = node
|
|
|
|
c.weight += node.weight
|
|
|
|
|
|
|
|
for ; c.tail != nil && c.tail != c.head && c.weight > c.budget; c.tail = c.tail.prev {
|
|
|
|
c.weight -= c.tail.weight
|
|
|
|
c.tail.prev.next = nil
|
2019-12-26 11:13:05 -05:00
|
|
|
|
|
|
|
if c.verbose {
|
|
|
|
log.Printf(
|
2020-01-31 23:18:11 -05:00
|
|
|
"warning -- Cache is evicting %s (%d) for %s (%d); spare weight is now %d",
|
2019-12-26 11:13:05 -05:00
|
|
|
c.tail.key,
|
|
|
|
c.tail.weight,
|
|
|
|
key,
|
|
|
|
weight,
|
|
|
|
c.budget-c.weight,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2019-12-16 22:23:01 -05:00
|
|
|
delete(c.lookup, c.tail.key)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-07-03 21:23:44 -04:00
|
|
|
// Retrieve gets an object out of the cache
|
2020-01-31 23:18:11 -05:00
|
|
|
func (c *Cache) Retrieve(key string) (interface{}, bool) {
|
2019-12-16 22:23:01 -05:00
|
|
|
c.mutex.Lock()
|
|
|
|
defer c.mutex.Unlock()
|
|
|
|
|
|
|
|
node, found := c.lookup[key]
|
|
|
|
if !found {
|
|
|
|
return nil, false
|
|
|
|
}
|
|
|
|
|
|
|
|
if node != c.head {
|
|
|
|
if node.next != nil {
|
|
|
|
node.next.prev = node.prev
|
|
|
|
}
|
2020-07-08 09:16:56 -04:00
|
|
|
|
2019-12-16 22:23:01 -05:00
|
|
|
if node.prev != nil {
|
|
|
|
node.prev.next = node.next
|
|
|
|
}
|
|
|
|
|
|
|
|
if node == c.tail {
|
|
|
|
c.tail = c.tail.prev
|
|
|
|
}
|
|
|
|
|
|
|
|
node.next = c.head
|
|
|
|
node.prev = nil
|
|
|
|
|
|
|
|
if c.head != nil {
|
|
|
|
c.head.prev = node
|
|
|
|
}
|
|
|
|
|
|
|
|
c.head = node
|
|
|
|
}
|
|
|
|
|
|
|
|
return node.value, true
|
|
|
|
}
|
2019-12-26 11:13:05 -05:00
|
|
|
|
2020-07-03 21:23:44 -04:00
|
|
|
// Clear removes all cache entries
|
2020-01-31 23:18:11 -05:00
|
|
|
func (c *Cache) Clear() {
|
2019-12-26 11:13:05 -05:00
|
|
|
c.mutex.Lock()
|
|
|
|
defer c.mutex.Unlock()
|
|
|
|
|
|
|
|
c.head = nil
|
|
|
|
c.tail = nil
|
|
|
|
c.lookup = make(map[string]*cacheNode)
|
|
|
|
c.weight = 0
|
|
|
|
}
|