* Update to rustfmt-preview. * Run "cargo fmt --all" with rustfmt 0.4.1. rustfmt 0.4.1 is the latest release of rustfmt-preview available on the stable channel. * Fix a long line that rustfmt 0.4.1 can't handle. * Remove unneeded commas left behind by rustfmt.
218 lines
7.4 KiB
Rust
218 lines
7.4 KiB
Rust
//! B+-tree node pool.
|
|
|
|
use super::{Forest, Node, NodeData};
|
|
use entity::PrimaryMap;
|
|
#[cfg(test)]
|
|
use std::fmt;
|
|
use std::ops::{Index, IndexMut};
|
|
|
|
/// A pool of nodes, including a free list.
|
|
pub(super) struct NodePool<F: Forest> {
|
|
nodes: PrimaryMap<Node, NodeData<F>>,
|
|
freelist: Option<Node>,
|
|
}
|
|
|
|
impl<F: Forest> NodePool<F> {
|
|
/// Allocate a new empty pool of nodes.
|
|
pub fn new() -> Self {
|
|
Self {
|
|
nodes: PrimaryMap::new(),
|
|
freelist: None,
|
|
}
|
|
}
|
|
|
|
/// Free all nodes.
|
|
pub fn clear(&mut self) {
|
|
self.nodes.clear();
|
|
self.freelist = None;
|
|
}
|
|
|
|
/// Allocate a new node containing `data`.
|
|
pub fn alloc_node(&mut self, data: NodeData<F>) -> Node {
|
|
debug_assert!(!data.is_free(), "can't allocate free node");
|
|
match self.freelist {
|
|
Some(node) => {
|
|
// Remove this node from the free list.
|
|
match self.nodes[node] {
|
|
NodeData::Free { next } => self.freelist = next,
|
|
_ => panic!("Invalid {} on free list", node),
|
|
}
|
|
self.nodes[node] = data;
|
|
node
|
|
}
|
|
None => {
|
|
// The free list is empty. Allocate a new node.
|
|
self.nodes.push(data)
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Free a node.
|
|
pub fn free_node(&mut self, node: Node) {
|
|
// Quick check for a double free.
|
|
debug_assert!(!self.nodes[node].is_free(), "{} is already free", node);
|
|
self.nodes[node] = NodeData::Free {
|
|
next: self.freelist,
|
|
};
|
|
self.freelist = Some(node);
|
|
}
|
|
|
|
/// Free the entire tree rooted at `node`.
|
|
pub fn free_tree(&mut self, node: Node) {
|
|
if let NodeData::Inner { size, tree, .. } = self[node] {
|
|
// Note that we have to capture `tree` by value to avoid borrow checker trouble.
|
|
#[cfg_attr(feature = "cargo-clippy", allow(needless_range_loop))]
|
|
for i in 0..usize::from(size + 1) {
|
|
// Recursively free sub-trees. This recursion can never be deeper than `MAX_PATH`,
|
|
// and since most trees have less than a handful of nodes, it is worthwhile to
|
|
// avoid the heap allocation for an iterative tree traversal.
|
|
self.free_tree(tree[i]);
|
|
}
|
|
}
|
|
self.free_node(node);
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
impl<F: Forest> NodePool<F> {
|
|
/// Verify the consistency of the tree rooted at `node`.
|
|
pub fn verify_tree(&self, node: Node, comp: &F::Comparator)
|
|
where
|
|
NodeData<F>: fmt::Display,
|
|
F::Key: fmt::Display,
|
|
{
|
|
use super::Comparator;
|
|
use entity::SparseSet;
|
|
use std::borrow::Borrow;
|
|
use std::cmp::Ordering;
|
|
use std::vec::Vec;
|
|
|
|
// The root node can't be an inner node with just a single sub-tree. It should have been
|
|
// pruned.
|
|
if let &NodeData::Inner { size, .. } = &self[node] {
|
|
assert!(size > 0, "Root must have more than one sub-tree");
|
|
}
|
|
|
|
let mut done = SparseSet::new();
|
|
let mut todo = Vec::new();
|
|
|
|
// Todo-list entries are:
|
|
// 1. Optional LHS key which must be <= all node entries.
|
|
// 2. The node reference.
|
|
// 3. Optional RHS key which must be > all node entries.
|
|
todo.push((None, node, None));
|
|
|
|
while let Some((lkey, node, rkey)) = todo.pop() {
|
|
assert_eq!(
|
|
done.insert(node),
|
|
None,
|
|
"Node appears more than once in tree"
|
|
);
|
|
let mut lower = lkey;
|
|
|
|
match self[node] {
|
|
NodeData::Inner { size, keys, tree } => {
|
|
let size = size as usize;
|
|
let capacity = tree.len();
|
|
let keys = &keys[0..size];
|
|
|
|
// Verify occupancy.
|
|
// Right-most nodes can be small, but others must be at least half full.
|
|
assert!(
|
|
rkey.is_none() || (size + 1) * 2 >= capacity,
|
|
"Only {}/{} entries in {}:{}, upper={}",
|
|
size + 1,
|
|
capacity,
|
|
node,
|
|
self[node],
|
|
rkey.unwrap()
|
|
);
|
|
|
|
// Queue up the sub-trees, checking for duplicates.
|
|
for i in 0..size + 1 {
|
|
// Get an upper bound for node[i].
|
|
let upper = keys.get(i).cloned().or(rkey);
|
|
|
|
// Check that keys are strictly monotonic.
|
|
if let (Some(a), Some(b)) = (lower, upper) {
|
|
assert_eq!(
|
|
comp.cmp(a, b),
|
|
Ordering::Less,
|
|
"Key order {} < {} failed in {}: {}",
|
|
a,
|
|
b,
|
|
node,
|
|
self[node]
|
|
);
|
|
}
|
|
|
|
// Queue up the sub-tree.
|
|
todo.push((lower, tree[i], upper));
|
|
|
|
// Set a lower bound for the next tree.
|
|
lower = upper;
|
|
}
|
|
}
|
|
NodeData::Leaf { size, keys, .. } => {
|
|
let size = size as usize;
|
|
let capacity = keys.borrow().len();
|
|
let keys = &keys.borrow()[0..size];
|
|
|
|
// Verify occupancy.
|
|
// Right-most nodes can be small, but others must be at least half full.
|
|
assert!(size > 0, "Leaf {} is empty", node);
|
|
assert!(
|
|
rkey.is_none() || size * 2 >= capacity,
|
|
"Only {}/{} entries in {}:{}, upper={}",
|
|
size,
|
|
capacity,
|
|
node,
|
|
self[node],
|
|
rkey.unwrap()
|
|
);
|
|
|
|
for i in 0..size + 1 {
|
|
let upper = keys.get(i).cloned().or(rkey);
|
|
|
|
// Check that keys are strictly monotonic.
|
|
if let (Some(a), Some(b)) = (lower, upper) {
|
|
let wanted = if i == 0 {
|
|
Ordering::Equal
|
|
} else {
|
|
Ordering::Less
|
|
};
|
|
assert_eq!(
|
|
comp.cmp(a, b),
|
|
wanted,
|
|
"Key order for {} - {} failed in {}: {}",
|
|
a,
|
|
b,
|
|
node,
|
|
self[node]
|
|
);
|
|
}
|
|
|
|
// Set a lower bound for the next key.
|
|
lower = upper;
|
|
}
|
|
}
|
|
NodeData::Free { .. } => panic!("Free {} reached", node),
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<F: Forest> Index<Node> for NodePool<F> {
|
|
type Output = NodeData<F>;
|
|
|
|
fn index(&self, index: Node) -> &Self::Output {
|
|
self.nodes.index(index)
|
|
}
|
|
}
|
|
|
|
impl<F: Forest> IndexMut<Node> for NodePool<F> {
|
|
fn index_mut(&mut self, index: Node) -> &mut Self::Output {
|
|
self.nodes.index_mut(index)
|
|
}
|
|
}
|