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:
		
				
					committed by
					
						 Dietmar Maurer
						Dietmar Maurer
					
				
			
			
				
	
			
			
			
						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()); | ||||
| } | ||||
|  | ||||
		Reference in New Issue
	
	Block a user