use std::collections::HashSet;
use crate::process::{
process_type::ProcessType,
step::{TransformStep, get_transform_step},
};
#[derive(Clone)]
pub(crate) struct ProcessTypeBitNode {
pub(crate) process_type_bit: ProcessType,
pub(crate) children: Vec<usize>,
pub(crate) step: Option<&'static TransformStep>,
pub(crate) process_type_index_mask: u64,
}
impl ProcessTypeBitNode {
pub(crate) fn heap_bytes(&self) -> usize {
self.children.capacity() * size_of::<usize>()
}
}
pub(crate) fn build_process_type_tree(
process_type_set: &HashSet<ProcessType>,
process_type_index_table: &[u8; 128],
) -> Vec<ProcessTypeBitNode> {
let max_nodes: usize = 1 + process_type_set
.iter()
.map(|pt| pt.bits().count_ones() as usize)
.sum::<usize>();
let mut process_type_tree = Vec::with_capacity(max_nodes);
let mut root = ProcessTypeBitNode {
process_type_bit: ProcessType::None,
children: Vec::new(),
step: None,
process_type_index_mask: 0,
};
if process_type_set.contains(&ProcessType::None) {
root.process_type_index_mask |=
1u64 << process_type_index_table[ProcessType::None.bits() as usize];
}
process_type_tree.push(root);
for &process_type in process_type_set.iter() {
let pt_mask_bit = 1u64 << process_type_index_table[process_type.bits() as usize];
let mut current_node_index = 0;
for process_type_bit in process_type.iter() {
let current_node = &process_type_tree[current_node_index];
if current_node.process_type_bit == process_type_bit {
continue;
}
let found_child = current_node
.children
.iter()
.find(|&&idx| process_type_tree[idx].process_type_bit == process_type_bit)
.copied();
if let Some(child_idx) = found_child {
current_node_index = child_idx;
process_type_tree[current_node_index].process_type_index_mask |= pt_mask_bit;
} else {
let child = ProcessTypeBitNode {
process_type_bit,
children: Vec::new(),
step: Some(get_transform_step(process_type_bit)),
process_type_index_mask: pt_mask_bit,
};
process_type_tree.push(child);
let new_node_index = process_type_tree.len() - 1;
process_type_tree[current_node_index]
.children
.push(new_node_index);
current_node_index = new_node_index;
}
}
}
process_type_tree
}
#[cfg(test)]
mod tests {
use super::*;
use crate::process::ProcessType;
fn identity_index_table() -> [u8; 128] {
let mut table = [u8::MAX; 128];
for i in 0..128u8 {
table[i as usize] = i;
}
table
}
#[test]
fn test_tree_single_none() {
let set: HashSet<ProcessType> = [ProcessType::None].into_iter().collect();
let tree = build_process_type_tree(&set, &identity_index_table());
assert_eq!(tree.len(), 1); assert!(tree[0].children.is_empty());
assert_ne!(
tree[0].process_type_index_mask, 0,
"root should have non-zero mask for None"
);
assert_eq!(tree[0].process_type_bit, ProcessType::None);
}
#[test]
fn test_tree_prefix_sharing() {
let set: HashSet<ProcessType> = [
ProcessType::VariantNorm,
ProcessType::VariantNorm | ProcessType::Delete,
]
.into_iter()
.collect();
let tree = build_process_type_tree(&set, &identity_index_table());
assert_eq!(tree.len(), 3);
assert_eq!(tree[0].children.len(), 1);
let fj_idx = tree[0].children[0];
assert_eq!(tree[fj_idx].process_type_bit, ProcessType::VariantNorm);
assert_eq!(tree[fj_idx].children.len(), 1);
let del_idx = tree[fj_idx].children[0];
assert_eq!(tree[del_idx].process_type_bit, ProcessType::Delete);
assert!(tree[del_idx].children.is_empty());
}
#[test]
fn test_delete_root_mask_unchanged() {
let table = identity_index_table();
let set: HashSet<ProcessType> = [ProcessType::Delete].into_iter().collect();
let tree = build_process_type_tree(&set, &table);
let del_bit = 1u64 << table[ProcessType::Delete.bits() as usize];
assert_eq!(
tree[0].process_type_index_mask & del_bit,
0,
"root should NOT carry Delete mask"
);
assert_eq!(tree[0].children.len(), 1);
assert_ne!(
tree[tree[0].children[0]].process_type_index_mask & del_bit,
0
);
}
}