-
-
Notifications
You must be signed in to change notification settings - Fork 684
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
8c95055
commit a04d849
Showing
2 changed files
with
185 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters