123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200 |
- package container_lru
- import "base:runtime"
- import "base:intrinsics"
- _ :: runtime
- _ :: intrinsics
- Node :: struct($Key, $Value: typeid) where intrinsics.type_is_valid_map_key(Key) {
- prev, next: ^Node(Key, Value),
- key: Key,
- value: Value,
- }
- // Cache is an LRU cache. It automatically removes entries as new entries are
- // added if the capacity is reached. Entries are removed based on how recently
- // they were used where the oldest entries are removed first.
- Cache :: struct($Key, $Value: typeid) where intrinsics.type_is_valid_map_key(Key) {
- head: ^Node(Key, Value),
- tail: ^Node(Key, Value),
- entries: map[Key]^Node(Key, Value),
- count: int,
- capacity: int,
- node_allocator: runtime.Allocator,
- on_remove: proc(key: Key, value: Value, user_data: rawptr),
- on_remove_user_data: rawptr,
- }
- // init initializes a Cache
- init :: proc(c: ^$C/Cache($Key, $Value), capacity: int, entries_allocator := context.allocator, node_allocator := context.allocator) {
- c.entries.allocator = entries_allocator
- c.node_allocator = node_allocator
- c.capacity = capacity
- }
- // destroy deinitializes a Cachem
- destroy :: proc(c: ^$C/Cache($Key, $Value), call_on_remove: bool) {
- clear(c, call_on_remove)
- delete(c.entries)
- }
- // clear the contents of a Cache
- clear :: proc(c: ^$C/Cache($Key, $Value), call_on_remove: bool) {
- for _, node in c.entries {
- if call_on_remove {
- _call_on_remove(c, node)
- }
- free(node, c.node_allocator)
- }
- runtime.clear(&c.entries)
- c.head = nil
- c.tail = nil
- c.count = 0
- }
- // set the given key value pair. This operation updates the recent usage of the item.
- set :: proc(c: ^$C/Cache($Key, $Value), key: Key, value: Value) -> runtime.Allocator_Error {
- if e, ok := c.entries[key]; ok {
- e.value = value
- _pop_node(c, e)
- _push_front_node(c, e)
- return nil
- }
- e : ^Node(Key, Value) = nil
- assert(c.count <= c.capacity)
- if c.count == c.capacity {
- e = c.tail
- _remove_node(c, e)
- } else {
- c.count += 1
- e = new(Node(Key, Value), c.node_allocator) or_return
- }
- e.key = key
- e.value = value
- _push_front_node(c, e)
- c.entries[key] = e
- return nil
- }
- // get a value from the cache from a given key. This operation updates the usage of the item.
- get :: proc(c: ^$C/Cache($Key, $Value), key: Key) -> (value: Value, ok: bool) #optional_ok {
- e: ^Node(Key, Value)
- e, ok = c.entries[key]
- if !ok {
- return
- }
- _pop_node(c, e)
- _push_front_node(c, e)
- return e.value, true
- }
- // get_ptr gets the pointer to a value the cache from a given key. This operation updates the usage of the item.
- get_ptr :: proc(c: ^$C/Cache($Key, $Value), key: Key) -> (value: ^Value, ok: bool) #optional_ok {
- e: ^Node(Key, Value)
- e, ok = c.entries[key]
- if !ok {
- return
- }
- _pop_node(c, e)
- _push_front_node(c, e)
- return &e.value, true
- }
- // peek gets the value from the cache from a given key without updating the recent usage.
- peek :: proc(c: ^$C/Cache($Key, $Value), key: Key) -> (value: Value, ok: bool) #optional_ok {
- e: ^Node(Key, Value)
- e, ok = c.entries[key]
- if !ok {
- return
- }
- return e.value, true
- }
- // exists checks for the existence of a value from a given key without updating the recent usage.
- exists :: proc(c: ^$C/Cache($Key, $Value), key: Key) -> bool {
- return key in c.entries
- }
- // remove removes an item from the cache.
- remove :: proc(c: ^$C/Cache($Key, $Value), key: Key) -> bool {
- e, ok := c.entries[key]
- if !ok {
- return false
- }
- _remove_node(c, e)
- free(node, c.node_allocator)
- c.count -= 1
- return true
- }
- @(private)
- _remove_node :: proc(c: ^$C/Cache($Key, $Value), node: ^Node(Key, Value)) {
- if c.head == node {
- c.head = node.next
- }
- if c.tail == node {
- c.tail = node.prev
- }
- if node.prev != nil {
- node.prev.next = node.next
- }
- if node.next != nil {
- node.next.prev = node.prev
- }
- node.prev = nil
- node.next = nil
- delete_key(&c.entries, node.key)
- _call_on_remove(c, node)
- }
- @(private)
- _call_on_remove :: proc(c: ^$C/Cache($Key, $Value), node: ^Node(Key, Value)) {
- if c.on_remove != nil {
- c.on_remove(node.key, node.value, c.on_remove_user_data)
- }
- }
- @(private)
- _push_front_node :: proc(c: ^$C/Cache($Key, $Value), e: ^Node(Key, Value)) {
- if c.head != nil {
- e.next = c.head
- e.next.prev = e
- }
- c.head = e
- if c.tail == nil {
- c.tail = e
- }
- e.prev = nil
- }
- @(private)
- _pop_node :: proc(c: ^$C/Cache($Key, $Value), e: ^Node(Key, Value)) {
- if e == nil {
- return
- }
- if c.head == e {
- c.head = e.next
- }
- if c.tail == e {
- c.tail = e.prev
- }
- if e.prev != nil {
- e.prev.next = e.next
- }
- if e.next != nil {
- e.next.prev = e.prev
- }
- e.prev = nil
- e.next = nil
- }
|