tools::lru_cache: Separate LinkedList part of LruCache into own struct.
In order to keep the separation more aparent and avoid mut borrow conflics. Signed-off-by: Christian Ebner <c.ebner@proxmox.com>
This commit is contained in:
parent
75c2ee7bab
commit
e3ab9a383c
@ -272,3 +272,164 @@ impl<K: std::cmp::Eq + std::hash::Hash + Copy, V> LruCache<K, V> {
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Linked list holding the nodes of the LruCache.
|
||||
///
|
||||
/// This struct actually holds the CacheNodes via the raw linked list pointers
|
||||
/// and allows to define the access sequence of these via the list sequence.
|
||||
/// The LinkedList of the standard library unfortunately does not implement
|
||||
/// an efficient way to bring list entries to the front, therefore we need our own.
|
||||
struct LinkedList<K, V> {
|
||||
head: *mut CacheNode<K, V>,
|
||||
tail: *mut CacheNode<K, V>,
|
||||
}
|
||||
|
||||
impl<K, V> LinkedList<K, V> {
|
||||
/// Create a new empty linked list.
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
head: std::ptr::null_mut(),
|
||||
tail: std::ptr::null_mut(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Bring the CacheNode referenced by `node_ptr` to the front of the linked list.
|
||||
fn bring_to_front(&mut self, node_ptr: *mut CacheNode<K, V>) {
|
||||
if node_ptr == self.head {
|
||||
// node is already head, just return
|
||||
return;
|
||||
}
|
||||
|
||||
let mut node = unsafe { Box::from_raw(node_ptr) };
|
||||
// Update the prev node to point to next (or null if current node is tail)
|
||||
unsafe { (*node.prev).next = node.next };
|
||||
|
||||
// Update the next node or otherwise the tail
|
||||
if !node.next.is_null() {
|
||||
unsafe { (*node.next).prev = node.prev };
|
||||
} else {
|
||||
// No next node means this was the tail
|
||||
self.tail = node.prev;
|
||||
}
|
||||
|
||||
node.prev = std::ptr::null_mut();
|
||||
node.next = self.head;
|
||||
// update the head and release ownership of the node again
|
||||
let node_ptr = Box::into_raw(node);
|
||||
// Update current head
|
||||
unsafe { (*self.head).prev = node_ptr };
|
||||
// Update to new head
|
||||
self.head = node_ptr;
|
||||
}
|
||||
|
||||
/// Insert a new node at the front of the linked list.
|
||||
fn push_front(&mut self, node_ptr: *mut CacheNode<K, V>) {
|
||||
let mut node = unsafe { Box::from_raw(node_ptr) };
|
||||
|
||||
// Old head gets new heads next
|
||||
node.next = self.head;
|
||||
// Release ownership of node, rest can be handled with just the pointer.
|
||||
let node_ptr = Box::into_raw(node);
|
||||
|
||||
// Update the prev for the old head
|
||||
if !self.head.is_null() {
|
||||
unsafe { (*self.head).prev = node_ptr };
|
||||
}
|
||||
|
||||
// Update the head to the new node pointer
|
||||
self.head = node_ptr;
|
||||
|
||||
// If there was no old tail, this node will be the new tail too
|
||||
if self.tail.is_null() {
|
||||
self.tail = node_ptr;
|
||||
}
|
||||
}
|
||||
|
||||
/// Remove the node referenced by `node_ptr` from the linke list and return it.
|
||||
fn remove(&mut self, node_ptr: *mut CacheNode<K, V>) -> Box<CacheNode<K, V>> {
|
||||
let node = unsafe { Box::from_raw(node_ptr) };
|
||||
|
||||
// Update the previous node or otherwise the head
|
||||
if !node.prev.is_null() {
|
||||
unsafe { (*node.prev).next = node.next };
|
||||
} else {
|
||||
// No previous node means this was the head
|
||||
self.head = node.next;
|
||||
}
|
||||
|
||||
// Update the next node or otherwise the tail
|
||||
if !node.next.is_null() {
|
||||
unsafe { (*node.next).prev = node.prev };
|
||||
} else {
|
||||
// No next node means this was the tail
|
||||
self.tail = node.prev;
|
||||
}
|
||||
node
|
||||
}
|
||||
|
||||
/// Remove the tail node from the linked list and return it.
|
||||
fn pop_tail(&mut self) -> Option<Box<CacheNode<K, V>>> {
|
||||
if self.tail.is_null() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let old_tail = unsafe { Box::from_raw(self.tail) };
|
||||
self.tail = old_tail.prev;
|
||||
// Update next node for new tail
|
||||
if !self.tail.is_null() {
|
||||
unsafe { (*self.tail).next = std::ptr::null_mut() };
|
||||
}
|
||||
Some(old_tail)
|
||||
}
|
||||
|
||||
/// Clear the linked list and free all the nodes.
|
||||
fn clear(&mut self) {
|
||||
let mut next = self.head;
|
||||
while !next.is_null() {
|
||||
// Taking ownership of node and drop it at the end of the block.
|
||||
let current = unsafe { Box::from_raw(next) };
|
||||
next = current.next;
|
||||
}
|
||||
// Reset head and tail pointers
|
||||
self.head = std::ptr::null_mut();
|
||||
self.tail = std::ptr::null_mut();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_linked_list() {
|
||||
let mut list = LinkedList::new();
|
||||
for idx in 0..3 {
|
||||
let node = Box::new(CacheNode::new(idx, idx + 1));
|
||||
// Get pointer, release ownership.
|
||||
let node_ptr = Box::into_raw(node);
|
||||
list.push_front(node_ptr);
|
||||
}
|
||||
assert_eq!(unsafe { (*list.head).key }, 2);
|
||||
assert_eq!(unsafe { (*list.head).value }, 3);
|
||||
assert_eq!(unsafe { (*list.tail).key }, 0);
|
||||
assert_eq!(unsafe { (*list.tail).value }, 1);
|
||||
|
||||
list.bring_to_front(list.tail);
|
||||
assert_eq!(unsafe { (*list.head).key }, 0);
|
||||
assert_eq!(unsafe { (*list.head).value }, 1);
|
||||
assert_eq!(unsafe { (*list.tail).key }, 1);
|
||||
assert_eq!(unsafe { (*list.tail).value }, 2);
|
||||
|
||||
list.bring_to_front(list.tail);
|
||||
assert_eq!(unsafe { (*list.head).key }, 1);
|
||||
assert_eq!(unsafe { (*list.head).value }, 2);
|
||||
assert_eq!(unsafe { (*list.tail).key }, 2);
|
||||
assert_eq!(unsafe { (*list.tail).value }, 3);
|
||||
|
||||
let tail = list.pop_tail().unwrap();
|
||||
assert_eq!(tail.key, 2);
|
||||
assert_eq!(tail.value, 3);
|
||||
assert_eq!(unsafe { (*list.head).key }, 1);
|
||||
assert_eq!(unsafe { (*list.head).value }, 2);
|
||||
assert_eq!(unsafe { (*list.tail).key }, 0);
|
||||
assert_eq!(unsafe { (*list.tail).value }, 1);
|
||||
|
||||
list.clear();
|
||||
assert!(list.head.is_null());
|
||||
assert!(list.tail.is_null());
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user