Files
VEFontCache-Odin/vefontcache/LRU.odin
2024-06-30 22:05:12 -04:00

244 lines
5.8 KiB
Odin

package VEFontCache
/*
The choice was made to keep the LRU cache implementation as close to the original as possible.
*/
import "base:runtime"
PoolListIter :: i32
PoolListValue :: u64
PoolListItem :: struct {
prev : PoolListIter,
next : PoolListIter,
value : PoolListValue,
}
PoolList :: struct {
items : [dynamic]PoolListItem,
free_list : [dynamic]PoolListIter,
front : PoolListIter,
back : PoolListIter,
size : i32,
capacity : i32,
dbg_name : string,
}
pool_list_init :: proc( pool : ^PoolList, capacity : i32, dbg_name : string = "" )
{
error : AllocatorError
pool.items, error = make( [dynamic]PoolListItem, int(capacity) )
assert( error == .None, "VEFontCache.pool_list_init : Failed to allocate items array")
resize( & pool.items, capacity )
pool.free_list, error = make( [dynamic]PoolListIter, len = 0, cap = int(capacity) )
assert( error == .None, "VEFontCache.pool_list_init : Failed to allocate free_list array")
resize( & pool.free_list, capacity )
pool.capacity = capacity
pool.dbg_name = dbg_name
using pool
for id in 0 ..< capacity {
free_list[id] = i32(id)
items[id] = {
prev = -1,
next = -1,
}
}
front = -1
back = -1
}
pool_list_free :: proc( pool : ^PoolList ) {
// TODO(Ed): Implement
}
pool_list_reload :: proc( pool : ^PoolList, allocator : Allocator ) {
reload_array( & pool.items, allocator )
reload_array( & pool.free_list, allocator )
}
pool_list_push_front :: proc( pool : ^PoolList, value : PoolListValue )
{
using pool
if size >= capacity do return
length := len(free_list)
assert( length > 0 )
assert( length == int(capacity - size) )
id := free_list[ len(free_list) - 1 ]
if pool.dbg_name != "" {
logf("pool_list: back %v", id)
}
pop( & free_list )
items[ id ].prev = -1
items[ id ].next = front
items[ id ].value = value
if pool.dbg_name != "" {
logf("pool_list: pushed %v into id %v", value, id)
}
if front != -1 do items[ front ].prev = id
if back == -1 do back = id
front = id
size += 1
}
pool_list_erase :: proc( pool : ^PoolList, iter : PoolListIter )
{
using pool
if size <= 0 do return
assert( iter >= 0 && iter < i32(capacity) )
assert( len(free_list) == int(capacity - size) )
iter_node := & items[ iter ]
prev := iter_node.prev
next := iter_node.next
if iter_node.prev != -1 do items[ prev ].next = iter_node.next
if iter_node.next != -1 do items[ next ].prev = iter_node.prev
if front == iter do front = iter_node.next
if back == iter do back = iter_node.prev
iter_node.prev = -1
iter_node.next = -1
iter_node.value = 0
append( & free_list, iter )
size -= 1
if size == 0 {
back = -1
front = -1
}
}
pool_list_move_to_front :: #force_inline proc( pool : ^PoolList, iter : PoolListIter )
{
using pool
if front == iter do return
item := & items[iter]
if item.prev != -1 do items[ item.prev ].next = item.next
if item.next != -1 do items[ item.next ].prev = item.prev
if back == iter do back = item.prev
item.prev = -1
item.next = front
items[ front ].prev = iter
front = iter
}
pool_list_peek_back :: #force_inline proc ( pool : ^PoolList ) -> PoolListValue {
assert( pool.back != - 1 )
value := pool.items[ pool.back ].value
return value
}
pool_list_pop_back :: #force_inline proc( pool : ^PoolList ) -> PoolListValue {
if pool.size <= 0 do return 0
assert( pool.back != -1 )
value := pool.items[ pool.back ].value
pool_list_erase( pool, pool.back )
return value
}
LRU_Link :: struct {
pad_top : u64,
value : i32,
ptr : PoolListIter,
pad_bottom : u64,
}
LRU_Cache :: struct {
capacity : i32,
num : i32,
table : map[u64]LRU_Link,
key_queue : PoolList,
}
LRU_init :: proc( cache : ^LRU_Cache, capacity : i32, dbg_name : string = "" ) {
error : AllocatorError
cache.capacity = capacity
cache.table, error = make( map[u64]LRU_Link, uint(capacity) )
assert( error == .None, "VEFontCache.LRU_init : Failed to allocate cache's table")
pool_list_init( & cache.key_queue, capacity, dbg_name = dbg_name )
}
LRU_free :: proc( cache : ^LRU_Cache ) {
// TODO(Ed): Implement
}
LRU_reload :: #force_inline proc( cache : ^LRU_Cache, allocator : Allocator ) {
reload_map( & cache.table, allocator )
pool_list_reload( & cache.key_queue, allocator )
}
LRU_find :: #force_inline proc "contextless" ( cache : ^LRU_Cache, key : u64, must_find := false ) -> (LRU_Link, bool) {
link, success := cache.table[key]
return link, success
}
LRU_get :: #force_inline proc( cache: ^LRU_Cache, key : u64 ) -> i32 {
if link, ok := &cache.table[ key ]; ok {
pool_list_move_to_front(&cache.key_queue, link.ptr)
return link.value
}
return -1
}
LRU_get_next_evicted :: #force_inline proc ( cache : ^LRU_Cache ) -> u64 {
if cache.key_queue.size >= cache.capacity {
evict := pool_list_peek_back( & cache.key_queue )
return evict
}
return 0xFFFFFFFFFFFFFFFF
}
LRU_peek :: #force_inline proc ( cache : ^LRU_Cache, key : u64, must_find := false ) -> i32 {
iter, success := LRU_find( cache, key, must_find )
if success == false {
return -1
}
return iter.value
}
LRU_put :: #force_inline proc( cache : ^LRU_Cache, key : u64, value : i32 ) -> u64
{
if link, ok := & cache.table[ key ]; ok {
pool_list_move_to_front( & cache.key_queue, link.ptr )
link.value = value
return key
}
evict := key
if cache.key_queue.size >= cache.capacity {
evict = pool_list_pop_back(&cache.key_queue)
delete_key(&cache.table, evict)
cache.num -= 1
}
pool_list_push_front(&cache.key_queue, key)
cache.table[key] = LRU_Link{
value = value,
ptr = cache.key_queue.front,
}
cache.num += 1
return evict
}
LRU_refresh :: proc( cache : ^LRU_Cache, key : u64 ) {
link, success := LRU_find( cache, key )
pool_list_erase( & cache.key_queue, link.ptr )
pool_list_push_front( & cache.key_queue, key )
link.ptr = cache.key_queue.front
}