use std::ops::ControlFlow;
use super::super::{
GroupRecord, NodeRecord, NodeSlotUpdate, RootNodeIds, SlotTable, SlotWriteSession,
};
use crate::NodeId;
impl SlotTable {
pub(in crate::slot) fn subtree_root_node_ids_at(&self, group_index: usize) -> RootNodeIds {
let mut roots = RootNodeIds::new();
self.for_each_subtree_root_node_id_at(group_index, |id| roots.push(id));
roots
}
pub(in crate::slot) fn for_each_subtree_root_node_id_at(
&self,
group_index: usize,
mut f: impl FnMut(NodeId),
) {
let Some(parent) = self
.subtree_first_record_at(group_index)
.map(|first| first.parent_id)
else {
return;
};
#[cfg(any(test, debug_assertions))]
self.debug_assert_roots_in_emission_order(group_index, parent);
let _ = self.try_for_each_root_in(group_index, parent, &mut |id| {
f(id);
ControlFlow::<()>::Continue(())
});
}
pub(in crate::slot) fn first_subtree_root_node_id_at(
&self,
group_index: usize,
) -> Option<NodeId> {
let first = self.subtree_first_record_at(group_index)?;
let found = if first.child_groups_before == 0 {
Some(first.id)
} else {
self.try_for_each_root_in(group_index, first.parent_id, &mut ControlFlow::Break)
.break_value()
};
#[cfg(any(test, debug_assertions))]
if crate::slot_validation_diagnostics_enabled() {
assert_eq!(
found,
self.try_for_each_root_in(group_index, first.parent_id, &mut ControlFlow::Break)
.break_value(),
"the first root must be the first one the subtree emitted"
);
}
found
}
fn subtree_first_record_at(&self, group_index: usize) -> Option<&NodeRecord> {
let group = self.groups.get(group_index)?;
if group.subtree_node_count == 0 {
return None;
}
let first = self.nodes.get(group.node_start as usize);
if first.is_none() {
log::error!(
"slot table ignored root-node collection past node storage at group index {group_index}"
);
}
first
}
fn own_node_records(&self, group: &GroupRecord) -> &[NodeRecord] {
let start = group.node_start as usize;
start
.checked_add(group.node_len as usize)
.and_then(|end| self.nodes.get(start..end))
.unwrap_or_default()
}
fn try_for_each_root_in<B>(
&self,
group_index: usize,
parent: Option<NodeId>,
f: &mut impl FnMut(NodeId) -> ControlFlow<B>,
) -> ControlFlow<B> {
let Some(group) = self.groups.get(group_index) else {
return ControlFlow::Continue(());
};
let own = self.own_node_records(group);
let mut child_nodes = group.subtree_node_count.saturating_sub(group.node_len);
let mut next_own = 0;
if child_nodes > 0 {
let end = group_index.saturating_add(group.subtree_len as usize);
let mut child_index = group_index + 1;
let mut children_before = 0;
while child_nodes > 0 && child_index < end {
while let Some(record) = own
.get(next_own)
.filter(|record| record.child_groups_before <= children_before)
{
if record.parent_id == parent {
f(record.id)?;
}
next_own += 1;
}
let Some(child) = self.groups.get(child_index) else {
log::error!(
"slot table stopped root-node collection at missing child group index {child_index}"
);
break;
};
if child.subtree_node_count > 0 {
let first_parent = self
.nodes
.get(child.node_start as usize)
.map(|record| record.parent_id);
if first_parent == Some(parent) {
self.try_for_each_root_in(child_index, parent, f)?;
}
child_nodes = child_nodes.saturating_sub(child.subtree_node_count);
}
children_before += 1;
child_index += (child.subtree_len as usize).max(1);
}
}
for record in &own[next_own..] {
if record.parent_id == parent {
f(record.id)?;
}
}
ControlFlow::Continue(())
}
#[cfg(any(test, debug_assertions))]
fn debug_assert_roots_in_emission_order(&self, group_index: usize, parent: Option<NodeId>) {
if !crate::slot_validation_diagnostics_enabled() {
return;
}
let Some(group) = self.groups.get(group_index) else {
return;
};
let start = group.node_start as usize;
let records = start
.checked_add(group.subtree_node_count as usize)
.and_then(|end| self.nodes.get(start..end))
.unwrap_or_default();
let mut emitted = Vec::new();
let _ = self.try_for_each_root_in(group_index, parent, &mut |id| {
emitted.push(id);
ControlFlow::<()>::Continue(())
});
let mut stored: Vec<_> = super::super::types::root_node_ids(records).collect();
emitted.sort_unstable();
stored.sort_unstable();
assert_eq!(
emitted, stored,
"the roots found in emission order must be the subtree's root records"
);
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) struct FoundNodeRecord {
index: usize,
pub(crate) id: NodeId,
pub(crate) generation: u32,
}
impl SlotWriteSession<'_> {
fn open_node_group(&self) -> Option<(usize, usize)> {
let frame = self.state.group_stack.last()?;
let group_index = self
.table
.open_group_index(frame.group_anchor, frame.group_index)?;
Some((group_index, frame.node_cursor))
}
pub(crate) fn record_node_with_parent(
&mut self,
id: NodeId,
generation: u32,
parent_id: Option<NodeId>,
source: crate::Key,
) -> NodeSlotUpdate {
let source = self.state.mix_branch_fold(source);
let Some(frame) = self.state.group_stack.last() else {
log::error!(
"slot writer record_node_with_parent called with an empty group stack; id={id}"
);
return NodeSlotUpdate::Inserted { id, generation };
};
let (group_anchor, node_cursor) = (frame.group_anchor, frame.node_cursor);
let result = match self.table.open_group_index(group_anchor, frame.group_index) {
Some(group_index) => self.table.record_node_at(
group_index,
node_cursor,
NodeRecord::active(
group_anchor,
id,
parent_id,
generation,
source,
frame.ended_children,
),
),
None => {
log::error!(
"slot table ignored node record for stale owner anchor {group_anchor:?}; node id={id}"
);
NodeSlotUpdate::Inserted { id, generation }
}
};
if let Some(frame) = self.state.group_stack.last_mut() {
frame.advance_node_cursor();
}
result
}
pub(crate) fn peek_node_record_by_source(
&mut self,
source: crate::Key,
skip_matches: usize,
) -> Option<FoundNodeRecord> {
let mixed = self.state.mix_branch_fold(source);
let (group_index, cursor) = self.open_node_group()?;
let mut from = cursor;
let mut remaining = skip_matches;
loop {
let (index, id, generation) =
self.table
.find_node_record_by_source_at(group_index, from, mixed)?;
if remaining == 0 {
return Some(FoundNodeRecord {
index,
id,
generation,
});
}
remaining -= 1;
from = index + 1;
}
}
pub(crate) fn adopt_node_record(
&mut self,
found: FoundNodeRecord,
parent_id: Option<NodeId>,
source: crate::Key,
) -> NodeSlotUpdate {
if let Some((group_index, cursor)) = self.open_node_group()
&& found.index > cursor
{
self.table
.rotate_node_record_to_cursor_at(group_index, found.index, cursor);
}
self.record_node_with_parent(found.id, found.generation, parent_id, source)
}
#[cfg(test)]
pub(crate) fn current_node_record(&mut self) -> Option<(NodeId, u32, crate::Key)> {
let frame = self.state.group_stack.last()?;
self.table
.node_identity_at_cursor(frame.group_anchor, frame.node_cursor)
}
}