use std::sync::Arc;
use petgraph::graph::NodeIndex;
use crate::datatypes::Value;
use crate::graph::schema::TypeIdIndex;
#[inline]
fn tombstone() -> NodeIndex {
NodeIndex::end()
}
#[derive(Debug, Clone)]
pub enum TypeEntry {
Owned(TypeIdIndex),
Layered {
base: Arc<TypeEntry>,
delta: TypeIdIndex,
depth: u16,
},
}
pub(crate) const MAX_CHAIN_DEPTH: u16 = 32;
const _: () = assert!(
MAX_CHAIN_DEPTH == 32,
"MAX_CHAIN_DEPTH is a measured value (D2 Phase 3 residual profile §B), not a \
free parameter — re-measure the held-view cell's mean before moving it, and \
update the doc comment with the new numbers"
);
const _: () = assert!(
MAX_CHAIN_DEPTH as usize == super::type_index_layer::MAX_LAYER_DEPTH
&& MAX_CHAIN_DEPTH as usize == crate::graph::dir_graph::index_layer::MAX_LAYER_DEPTH,
"the layered-index depth caps have drifted apart: id_index_layer::MAX_CHAIN_DEPTH, \
type_index_layer::MAX_LAYER_DEPTH and dir_graph::index_layer::MAX_LAYER_DEPTH \
(shared with range_index_layer) each document themselves as matching the others, \
so they move together or the doc comments are lying"
);
impl Default for TypeEntry {
fn default() -> Self {
TypeEntry::Owned(TypeIdIndex::default())
}
}
impl From<TypeIdIndex> for TypeEntry {
#[inline]
fn from(index: TypeIdIndex) -> Self {
TypeEntry::Owned(index)
}
}
impl TypeEntry {
#[inline]
pub fn get(&self, id: &Value) -> Option<NodeIndex> {
match self {
TypeEntry::Owned(index) => index.get(id),
TypeEntry::Layered { base, delta, .. } => match delta.get(id) {
Some(idx) if idx == tombstone() => None,
Some(idx) => Some(idx),
None => base.get(id),
},
}
}
#[inline]
pub fn insert(&mut self, id: Value, idx: NodeIndex) {
match self {
TypeEntry::Owned(index) => index.insert(id, idx),
TypeEntry::Layered { delta, .. } => delta.insert(id, idx),
}
}
pub fn remove_matching(&mut self, id: &Value, idx: NodeIndex) -> bool {
match self {
TypeEntry::Owned(index) => index.remove_matching(id, idx),
TypeEntry::Layered { base, delta, .. } => {
if delta.get(id) == Some(tombstone()) {
return false;
}
let resolved = delta.get(id).or_else(|| base.get(id));
if resolved != Some(idx) {
return false;
}
delta.insert(id.clone(), tombstone());
true
}
}
}
pub fn len(&self) -> usize {
match self {
TypeEntry::Owned(index) => index.len(),
TypeEntry::Layered { base, delta, .. } => {
let mut live = base.len() as i64;
for (id, idx) in delta.iter() {
let in_base = base.get(&id).is_some();
if idx == tombstone() {
if in_base {
live -= 1;
}
} else if !in_base {
live += 1;
}
}
live.max(0) as usize
}
}
}
pub fn materialize(&self) -> TypeIdIndex {
match self {
TypeEntry::Owned(index) => index.clone(),
TypeEntry::Layered { base, delta, .. } => {
let mut merged = base.materialize();
for (id, idx) in delta.iter() {
if idx == tombstone() {
if let Some(current) = merged.get(&id) {
merged.remove_matching(&id, current);
}
} else {
merged.insert(id, idx);
}
}
merged
}
}
}
pub fn share(&mut self) -> Arc<TypeEntry> {
if self.depth() >= MAX_CHAIN_DEPTH {
*self = TypeEntry::Owned(self.materialize());
}
let taken = std::mem::replace(self, TypeEntry::Owned(TypeIdIndex::default()));
let depth = taken.depth().saturating_add(1);
let base = Arc::new(taken);
*self = TypeEntry::Layered {
base: Arc::clone(&base),
delta: TypeIdIndex::default(),
depth,
};
base
}
#[inline]
fn depth(&self) -> u16 {
match self {
TypeEntry::Owned(_) => 0,
TypeEntry::Layered { depth, .. } => *depth,
}
}
#[inline]
pub fn layered_over(base: Arc<TypeEntry>) -> Self {
let depth = base.depth().saturating_add(1);
TypeEntry::Layered {
base,
delta: TypeIdIndex::default(),
depth,
}
}
pub fn try_compact(&mut self) {
let TypeEntry::Layered { base, .. } = self else {
return;
};
if Arc::get_mut(base).is_none() {
return;
}
let TypeEntry::Layered { base, delta, .. } =
std::mem::replace(self, TypeEntry::Owned(TypeIdIndex::default()))
else {
unreachable!("just matched Layered")
};
let mut inner =
Arc::try_unwrap(base).unwrap_or_else(|_| unreachable!("get_mut proved uniqueness"));
inner.try_compact();
let mut owned = match inner {
TypeEntry::Owned(index) => index,
layered => layered.materialize(),
};
for (id, idx) in delta.iter() {
if idx == tombstone() {
if let Some(current) = owned.get(&id) {
owned.remove_matching(&id, current);
}
} else {
owned.insert(id, idx);
}
}
*self = TypeEntry::Owned(owned);
}
#[cfg(test)]
pub fn is_layered(&self) -> bool {
matches!(self, TypeEntry::Layered { .. })
}
}
#[cfg(test)]
mod tests {
use super::*;
fn owned(pairs: &[(u32, usize)]) -> TypeEntry {
let mut index = TypeIdIndex::default();
for (id, node) in pairs {
index.insert(Value::UniqueId(*id), NodeIndex::new(*node));
}
TypeEntry::Owned(index)
}
#[test]
fn layering_preserves_every_answer_the_owned_index_gives() {
let mut parent = owned(&[(1, 10), (2, 20), (3, 30)]);
let base = parent.share();
let child = TypeEntry::layered_over(base);
assert!(parent.is_layered() && child.is_layered());
for id in 1..=3u32 {
let v = Value::UniqueId(id);
assert_eq!(child.get(&v), Some(NodeIndex::new(id as usize * 10)));
assert_eq!(parent.get(&v), child.get(&v));
}
assert_eq!(child.get(&Value::UniqueId(9)), None);
assert_eq!(child.len(), 3);
assert_eq!(child.get(&Value::Int64(2)), Some(NodeIndex::new(20)));
}
#[test]
fn a_write_through_one_holder_is_invisible_to_the_other() {
let mut parent = owned(&[(1, 10), (2, 20)]);
let mut child = TypeEntry::layered_over(parent.share());
child.insert(Value::UniqueId(3), NodeIndex::new(30));
assert_eq!(child.get(&Value::UniqueId(3)), Some(NodeIndex::new(30)));
assert_eq!(
parent.get(&Value::UniqueId(3)),
None,
"the other holder must not see a delta write"
);
assert_eq!(child.len(), 3);
assert_eq!(parent.len(), 2);
child.insert(Value::UniqueId(1), NodeIndex::new(99));
assert_eq!(child.get(&Value::UniqueId(1)), Some(NodeIndex::new(99)));
assert_eq!(parent.get(&Value::UniqueId(1)), Some(NodeIndex::new(10)));
assert_eq!(child.len(), 3, "an overwrite is not a new entry");
}
#[test]
fn deletion_tombstones_rather_than_editing_the_shared_base() {
let mut parent = owned(&[(1, 10), (2, 20)]);
let mut child = TypeEntry::layered_over(parent.share());
assert!(child.remove_matching(&Value::UniqueId(1), NodeIndex::new(10)));
assert_eq!(child.get(&Value::UniqueId(1)), None);
assert_eq!(child.len(), 1);
assert_eq!(
parent.get(&Value::UniqueId(1)),
Some(NodeIndex::new(10)),
"the other holder keeps the entry"
);
assert!(!child.remove_matching(&Value::UniqueId(1), NodeIndex::new(10)));
assert_eq!(child.len(), 1);
assert!(!child.remove_matching(&Value::UniqueId(2), NodeIndex::new(77)));
assert_eq!(child.get(&Value::UniqueId(2)), Some(NodeIndex::new(20)));
}
#[test]
fn compaction_folds_the_delta_and_preserves_every_answer() {
let mut child = {
let mut parent = owned(&[(1, 10), (2, 20), (3, 30)]);
TypeEntry::layered_over(parent.share())
};
child.insert(Value::UniqueId(4), NodeIndex::new(40));
child.remove_matching(&Value::UniqueId(2), NodeIndex::new(20));
let before: Vec<_> = (1..=4u32)
.map(|id| child.get(&Value::UniqueId(id)))
.collect();
let len_before = child.len();
child.try_compact();
assert!(
!child.is_layered(),
"the last holder must collapse to Owned"
);
let after: Vec<_> = (1..=4u32)
.map(|id| child.get(&Value::UniqueId(id)))
.collect();
assert_eq!(after, before, "compaction must not change a single answer");
assert_eq!(child.len(), len_before);
assert_eq!(
after,
vec![
Some(NodeIndex::new(10)),
None,
Some(NodeIndex::new(30)),
Some(NodeIndex::new(40))
]
);
}
#[test]
fn compaction_declines_while_another_holder_is_alive() {
let mut parent = owned(&[(1, 10)]);
let mut child = TypeEntry::layered_over(parent.share());
child.insert(Value::UniqueId(2), NodeIndex::new(20));
child.try_compact();
assert!(child.is_layered(), "a live co-holder must block the fold");
assert_eq!(parent.get(&Value::UniqueId(2)), None);
drop(parent);
child.try_compact();
assert!(!child.is_layered());
assert_eq!(child.get(&Value::UniqueId(2)), Some(NodeIndex::new(20)));
}
#[test]
fn a_general_delta_over_an_integer_base_resolves_both_ways() {
let mut parent = owned(&[(1, 10), (2, 20)]);
let mut child = TypeEntry::layered_over(parent.share());
child.insert(Value::String("abc".to_string()), NodeIndex::new(30));
assert_eq!(
child.get(&Value::String("abc".to_string())),
Some(NodeIndex::new(30))
);
assert_eq!(child.get(&Value::UniqueId(1)), Some(NodeIndex::new(10)));
assert_eq!(child.get(&Value::Int64(2)), Some(NodeIndex::new(20)));
assert_eq!(child.len(), 3);
let merged = child.materialize();
assert_eq!(merged.get(&Value::UniqueId(1)), Some(NodeIndex::new(10)));
assert_eq!(
merged.get(&Value::String("abc".to_string())),
Some(NodeIndex::new(30))
);
}
#[test]
fn a_never_compacted_chain_stays_bounded_and_correct() {
let mut writer = owned(&[(0, 0)]);
let mut readers = Vec::new();
for round in 1..=(MAX_CHAIN_DEPTH as u32 * 3) {
readers.push(TypeEntry::layered_over(writer.share()));
writer.insert(Value::UniqueId(round), NodeIndex::new(round as usize));
}
assert!(
writer.depth() <= MAX_CHAIN_DEPTH,
"chain depth {} exceeded the cap",
writer.depth()
);
for round in 1..=(MAX_CHAIN_DEPTH as u32 * 3) {
assert_eq!(
writer.get(&Value::UniqueId(round)),
Some(NodeIndex::new(round as usize)),
"id {round} lost in the chain"
);
}
assert_eq!(writer.get(&Value::UniqueId(0)), Some(NodeIndex::new(0)));
assert_eq!(writer.len(), MAX_CHAIN_DEPTH as usize * 3 + 1);
assert_eq!(readers[0].get(&Value::UniqueId(1)), None);
assert_eq!(readers[0].get(&Value::UniqueId(0)), Some(NodeIndex::new(0)));
drop(readers);
writer.try_compact();
assert!(
!writer.is_layered(),
"a chain must collapse once nothing shares it"
);
assert_eq!(writer.len(), MAX_CHAIN_DEPTH as usize * 3 + 1);
}
#[test]
fn the_chain_flattens_on_the_thirty_third_uncompacted_fork() {
let mut writer = owned(&[(0, 0)]);
let mut readers = Vec::new();
let mut depths = Vec::new();
for round in 1..=34u32 {
readers.push(TypeEntry::layered_over(writer.share()));
writer.insert(Value::UniqueId(round), NodeIndex::new(round as usize));
depths.push(writer.depth());
}
let expected: Vec<u16> = (1..=32).chain([1, 2]).collect();
assert_eq!(
depths, expected,
"the chain must climb to exactly 32 and wrap on the 33rd fork"
);
for round in 1..=34u32 {
assert_eq!(
writer.get(&Value::UniqueId(round)),
Some(NodeIndex::new(round as usize)),
"id {round} lost across the flatten"
);
}
assert_eq!(writer.get(&Value::UniqueId(0)), Some(NodeIndex::new(0)));
assert_eq!(readers[0].get(&Value::UniqueId(1)), None);
}
}