Skip to content

Commit

Permalink
core:container/lru
Browse files Browse the repository at this point in the history
  • Loading branch information
gingerBill committed Feb 1, 2022
1 parent 8c95055 commit a04d849
Show file tree
Hide file tree
Showing 2 changed files with 185 additions and 1 deletion.
183 changes: 183 additions & 0 deletions core/container/lru/lru_cache.odin
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
package container_lru

import "core:intrinsics"
import "core:mem"
_ :: intrinsics
_ :: mem

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: mem.Allocator,

on_remove: proc(key: Key, value: Value, user_data: rawptr),
on_remove_user_data: rawptr,
call_on_remove_on_destroy: bool,
}

// 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 Cache
destroy :: proc(c: ^$C/Cache($Key, $Value)) {
for _, node in c.entries {
if c.call_on_remove_on_destroy && c.on_remove != nil {
c.on_remove(node.key, node.value, c.on_remove_user_data)
}
free(node, c.node_allocator)
}
clear(&c.entries)
delete(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) -> mem.Allocator_Error {
if e, ok := c.entries[key]; ok {
e.value = value
return nil
}

e := new(Node(Key, Value), c.node_allocator) or_return
e.key = key
e.value = value

_push_front_node(c, e)
if c.count > c.capacity {
_remove_node(c, c.tail)
}

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)
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

c.count -= 1

delete_key(&c.entries, node.key)

if c.on_remove != nil {
c.on_remove(node.key, node.value, c.on_remove_user_data)
}

free(node, c.node_allocator)

}

@(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

c.count += 1
}

@(private)
_pop_node :: proc(c: ^$C/Cache($Key, $Value), e: ^Node(Key, Value)) {
if e == nil {
return
}
if e.prev != nil {
e.prev.next = e.next
}

if e.next != nil {
e.next.prev = e.prev
}
e.prev = nil
e.next = nil
}
3 changes: 2 additions & 1 deletion examples/all/all_main.odin
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ import zlib "core:compress/zlib"
import bit_array "core:container/bit_array"
import priority_queue "core:container/priority_queue"
import queue "core:container/queue"
import small_array "core:container/queue"
import small_array "core:container/small_array"
import lru "core:container/lru"

import crypto "core:crypto"
import blake "core:crypto/blake"
Expand Down

0 comments on commit a04d849

Please sign in to comment.