mirror of
https://github.com/Ed94/VEFontCache-Odin.git
synced 2025-08-04 22:22:43 -07:00
288 lines
7.9 KiB
Odin
288 lines
7.9 KiB
Odin
package vefontcache
|
|
|
|
/* Note(Ed):
|
|
Original implementation has been changed moderately.
|
|
Notably the LRU is now type generic for its key value.
|
|
This was done to profile between using u64, u32, and u16.
|
|
|
|
What ended up happening was using u32 for both the atlas and the shape cache
|
|
yielded a several ms save for processing thousands of draw text calls.
|
|
|
|
There was an attempt at an optimization pass but the directives done here (other than force_inline)
|
|
are marginal changes at best.
|
|
*/
|
|
|
|
// 16-bit hashing was attempted, however it seems to get collisions with djb8_hash_16
|
|
|
|
LRU_Fail_Mask_16 :: 0xFFFF
|
|
LRU_Fail_Mask_32 :: 0xFFFFFFFF
|
|
LRU_Fail_Mask_64 :: 0xFFFFFFFFFFFFFFFF
|
|
|
|
Pool_ListIter :: i32
|
|
|
|
Pool_List_Item :: struct( $V_Type : typeid ) #packed {
|
|
// Pool_List_Item :: struct( $V_Type : typeid ) {
|
|
prev : Pool_ListIter,
|
|
next : Pool_ListIter,
|
|
value : V_Type,
|
|
}
|
|
|
|
Pool_List :: struct( $V_Type : typeid) {
|
|
items : [dynamic]Pool_List_Item(V_Type),
|
|
free_list : [dynamic]Pool_ListIter,
|
|
front : Pool_ListIter,
|
|
back : Pool_ListIter,
|
|
size : i32,
|
|
capacity : i32,
|
|
dbg_name : string,
|
|
}
|
|
|
|
pool_list_init :: proc( pool : ^Pool_List($V_Type), capacity : i32, dbg_name : string = "" )
|
|
{
|
|
error : Allocator_Error
|
|
pool.items, error = make( [dynamic]Pool_List_Item(V_Type), int(capacity) )
|
|
assert( error == .None, "VEFontCache.pool_list_inits: Failed to allocate items array")
|
|
resize( & pool.items, capacity )
|
|
|
|
pool.free_list, error = make( [dynamic]Pool_ListIter, 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
|
|
|
|
for id in 0 ..< pool.capacity {
|
|
pool.free_list[id] = Pool_ListIter(id)
|
|
pool.items[id] = {
|
|
prev = -1,
|
|
next = -1,
|
|
}
|
|
}
|
|
|
|
pool.front = -1
|
|
pool.back = -1
|
|
}
|
|
|
|
pool_list_free :: proc( pool : ^Pool_List($V_Type) ) {
|
|
delete( pool.items)
|
|
delete( pool.free_list)
|
|
}
|
|
|
|
pool_list_reload :: proc( pool : ^Pool_List($V_Type), allocator : Allocator ) {
|
|
reload_array( & pool.items, allocator )
|
|
reload_array( & pool.free_list, allocator )
|
|
}
|
|
|
|
pool_list_clear :: proc( pool: ^Pool_List($V_Type) )
|
|
{
|
|
clear(& pool.items)
|
|
clear(& pool.free_list)
|
|
resize( & pool.items, cap(pool.items) )
|
|
resize( & pool.free_list, cap(pool.free_list) )
|
|
|
|
for id in 0 ..< pool.capacity {
|
|
pool.free_list[id] = Pool_ListIter(id)
|
|
pool.items[id] = {
|
|
prev = -1,
|
|
next = -1,
|
|
}
|
|
}
|
|
|
|
pool.front = -1
|
|
pool.back = -1
|
|
pool.size = 0
|
|
}
|
|
|
|
@(optimization_mode="favor_size")
|
|
pool_list_push_front :: proc( pool : ^Pool_List($V_Type), value : V_Type ) #no_bounds_check
|
|
{
|
|
if pool.size >= pool.capacity do return
|
|
|
|
length := len(pool.free_list)
|
|
assert( length > 0 )
|
|
assert( length == int(pool.capacity - pool.size) )
|
|
|
|
id := pool.free_list[ len(pool.free_list) - 1 ]
|
|
// if pool.dbg_name != "" {
|
|
// logf("pool_list: back %v", id)
|
|
// }
|
|
pop( & pool.free_list )
|
|
pool.items[ id ].prev = -1
|
|
pool.items[ id ].next = pool.front
|
|
pool.items[ id ].value = value
|
|
// if pool.dbg_name != "" {
|
|
// logf("pool_list: pushed %v into id %v", value, id)
|
|
// }
|
|
|
|
if pool.front != -1 do pool.items[ pool.front ].prev = id
|
|
if pool.back == -1 do pool.back = id
|
|
pool.front = id
|
|
pool.size += 1
|
|
}
|
|
|
|
@(optimization_mode="favor_size")
|
|
pool_list_erase :: proc( pool : ^Pool_List($V_Type), iter : Pool_ListIter ) #no_bounds_check
|
|
{
|
|
if pool.size <= 0 do return
|
|
assert( iter >= 0 && iter < Pool_ListIter(pool.capacity) )
|
|
assert( len(pool.free_list) == int(pool.capacity - pool.size) )
|
|
|
|
iter_node := & pool.items[ iter ]
|
|
prev := iter_node.prev
|
|
next := iter_node.next
|
|
|
|
if iter_node.prev != -1 do pool.items[ prev ].next = iter_node.next
|
|
if iter_node.next != -1 do pool.items[ next ].prev = iter_node.prev
|
|
|
|
if pool.front == iter do pool.front = iter_node.next
|
|
if pool.back == iter do pool.back = iter_node.prev
|
|
|
|
iter_node.prev = -1
|
|
iter_node.next = -1
|
|
iter_node.value = 0
|
|
append( & pool.free_list, iter )
|
|
|
|
pool.size -= 1
|
|
if pool.size == 0 {
|
|
pool.back = -1
|
|
pool.front = -1
|
|
}
|
|
}
|
|
|
|
@(optimization_mode="favor_size")
|
|
pool_list_move_to_front :: proc "contextless" ( pool : ^Pool_List($V_Type), iter : Pool_ListIter ) #no_bounds_check
|
|
{
|
|
if pool.front == iter do return
|
|
|
|
item := & pool.items[iter]
|
|
if item.prev != -1 do pool.items[ item.prev ].next = item.next
|
|
if item.next != -1 do pool.items[ item.next ].prev = item.prev
|
|
if pool.back == iter do pool.back = item.prev
|
|
|
|
item.prev = -1
|
|
item.next = pool.front
|
|
pool.items[ pool.front ].prev = iter
|
|
pool.front = iter
|
|
}
|
|
|
|
@(optimization_mode="favor_size")
|
|
pool_list_peek_back :: #force_inline proc ( pool : Pool_List($V_Type) ) -> V_Type #no_bounds_check {
|
|
assert( pool.back != - 1 )
|
|
value := pool.items[ pool.back ].value
|
|
return value
|
|
}
|
|
|
|
@(optimization_mode="favor_size")
|
|
pool_list_pop_back :: #force_inline proc( pool : ^Pool_List($V_Type) ) -> V_Type #no_bounds_check {
|
|
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 #packed {
|
|
value : i32,
|
|
ptr : Pool_ListIter,
|
|
}
|
|
|
|
LRU_Cache :: struct( $Key_Type : typeid ) {
|
|
capacity : i32,
|
|
num : i32,
|
|
table : map[Key_Type]LRU_Link,
|
|
key_queue : Pool_List(Key_Type),
|
|
}
|
|
|
|
lru_init :: proc( cache : ^LRU_Cache($Key_Type), capacity : i32, dbg_name : string = "" ) {
|
|
error : Allocator_Error
|
|
cache.capacity = capacity
|
|
cache.table, error = make( map[Key_Type]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($Key_Type) ) {
|
|
pool_list_free( & cache.key_queue )
|
|
delete( cache.table )
|
|
}
|
|
|
|
lru_reload :: #force_inline proc( cache : ^LRU_Cache($Key_Type), allocator : Allocator ) {
|
|
reload_map( & cache.table, allocator )
|
|
pool_list_reload( & cache.key_queue, allocator )
|
|
}
|
|
|
|
lru_clear :: proc ( cache : ^LRU_Cache($Key_Type) ) {
|
|
pool_list_clear( & cache.key_queue )
|
|
clear(& cache.table)
|
|
cache.num = 0
|
|
}
|
|
|
|
@(optimization_mode="favor_size")
|
|
lru_find :: #force_inline proc "contextless" ( cache : LRU_Cache($Key_Type), key : Key_Type, must_find := false ) -> (LRU_Link, bool) #no_bounds_check {
|
|
link, success := cache.table[key]
|
|
return link, success
|
|
}
|
|
|
|
@(optimization_mode="favor_size")
|
|
lru_get :: #force_inline proc ( cache: ^LRU_Cache($Key_Type), key : Key_Type ) -> i32 #no_bounds_check {
|
|
if link, ok := &cache.table[ key ]; ok {
|
|
pool_list_move_to_front(&cache.key_queue, link.ptr)
|
|
return link.value
|
|
}
|
|
return -1
|
|
}
|
|
|
|
@(optimization_mode="favor_size")
|
|
lru_get_next_evicted :: #force_inline proc ( cache : LRU_Cache($Key_Type) ) -> Key_Type #no_bounds_check {
|
|
if cache.key_queue.size >= cache.capacity {
|
|
evict := pool_list_peek_back( cache.key_queue )
|
|
return evict
|
|
}
|
|
return ~Key_Type(0)
|
|
}
|
|
|
|
@(optimization_mode="favor_size")
|
|
lru_peek :: #force_inline proc "contextless" ( cache : LRU_Cache($Key_Type), key : Key_Type, must_find := false ) -> i32 #no_bounds_check {
|
|
iter, success := lru_find( cache, key, must_find )
|
|
if success == false {
|
|
return -1
|
|
}
|
|
return iter.value
|
|
}
|
|
|
|
@(optimization_mode="favor_size")
|
|
lru_put :: proc( cache : ^LRU_Cache($Key_Type), key : Key_Type, value : i32 ) -> Key_Type #no_bounds_check
|
|
{
|
|
// profile(#procedure)
|
|
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_Type), key : Key_Type ) {
|
|
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
|
|
}
|