use std::cell::RefCell;
use super::super::context::ParentList;
use super::{
NodeId,
node_hash::{NodeMap, NodeSet},
};
pub(super) struct ParentDiff {
pub became_watched: Vec<NodeId>,
pub became_unwatched: Vec<NodeId>,
}
pub(super) struct Edges {
child_parents: RefCell<NodeMap<NodeSet>>,
parent_children: RefCell<NodeMap<NodeSet>>,
parent_refs: RefCell<NodeMap<ParentList>>,
}
impl Edges {
pub(super) fn new() -> Self {
Self {
child_parents: RefCell::new(NodeMap::default()),
parent_children: RefCell::new(NodeMap::default()),
parent_refs: RefCell::new(NodeMap::default()),
}
}
pub(super) fn is_watched(&self, id: NodeId) -> bool {
self.parent_children
.borrow()
.get(&id)
.is_some_and(|c| !c.is_empty())
}
pub(super) fn has_parents(&self, id: NodeId) -> bool {
self.child_parents
.borrow()
.get(&id)
.is_some_and(|parents| !parents.is_empty())
}
pub(super) fn count_parents_if(&self, id: NodeId, mut pred: impl FnMut(NodeId) -> bool) -> u32 {
match self.child_parents.borrow().get(&id) {
Some(parents) => parents
.iter()
.copied()
.filter(|&parent| pred(parent))
.count() as u32,
None => 0,
}
}
pub(super) fn copy_children(&self, id: NodeId, buf: &mut Vec<NodeId>) {
buf.clear();
if let Some(set) = self.parent_children.borrow().get(&id) {
buf.extend(set.iter().copied());
}
}
pub(super) fn copy_parents(&self, id: NodeId, buf: &mut Vec<NodeId>) {
buf.clear();
if let Some(set) = self.child_parents.borrow().get(&id) {
buf.extend(set.iter().copied());
}
}
pub(super) fn replace(&self, child: NodeId, pairs: ParentList) -> Option<ParentDiff> {
{
let parent_refs = self.parent_refs.borrow();
if let Some(previous) = parent_refs.get(&child)
&& previous.len() == pairs.len()
&& previous
.iter()
.zip(pairs.iter())
.all(|((before, _), (now, _))| before == now)
{
return None;
}
}
let new_parents: NodeSet = pairs.iter().map(|(id, _)| *id).collect();
let mut became_watched = Vec::new();
let mut became_unwatched = Vec::new();
{
let child_parents = self.child_parents.borrow();
let old = child_parents.get(&child);
if old == Some(&new_parents) {
return None;
}
let mut parent_children = self.parent_children.borrow_mut();
if let Some(old) = old {
for parent in old.difference(&new_parents) {
if let Some(children) = parent_children.get_mut(parent) {
children.remove(&child);
if children.is_empty() {
became_unwatched.push(*parent);
}
}
}
}
for parent in &new_parents {
if old.is_some_and(|old| old.contains(parent)) {
continue;
}
let children = parent_children.entry(*parent).or_default();
let was_empty = children.is_empty();
children.insert(child);
if was_empty {
became_watched.push(*parent);
}
}
}
self.child_parents.borrow_mut().insert(child, new_parents);
let old_kept = self.parent_refs.borrow_mut().insert(child, pairs);
drop(old_kept);
Some(ParentDiff {
became_watched,
became_unwatched,
})
}
pub(super) fn unregister(&self, id: NodeId) -> Vec<NodeId> {
let _kept = self.parent_refs.borrow_mut().remove(&id);
let parents = self
.child_parents
.borrow_mut()
.remove(&id)
.unwrap_or_default();
let mut became_unwatched = Vec::new();
{
let mut parent_children = self.parent_children.borrow_mut();
for parent in &parents {
if let Some(children) = parent_children.get_mut(parent) {
children.remove(&id);
if children.is_empty() {
became_unwatched.push(*parent);
}
}
}
if let Some(children) = parent_children.remove(&id) {
let mut child_parents = self.child_parents.borrow_mut();
for child in children {
if let Some(ps) = child_parents.get_mut(&child) {
ps.remove(&id);
}
}
}
}
became_unwatched
}
}
#[cfg(test)]
mod tests {
use std::rc::Rc;
use super::{super::ErasedNode, *};
struct N;
impl ErasedNode for N {
fn refresh(&self) -> bool {
false
}
}
fn slot(id: u64) -> (NodeId, Rc<dyn ErasedNode>) {
(NodeId(id), Rc::new(N))
}
#[test]
fn is_watched_after_replace() {
let edges = Edges::new();
edges.replace(NodeId(2), vec![slot(1)]);
assert!(edges.is_watched(NodeId(1)));
}
#[test]
fn empty_children_is_not_watched() {
let edges = Edges::new();
edges.replace(NodeId(2), vec![slot(1)]);
edges.replace(NodeId(2), vec![]);
assert!(!edges.is_watched(NodeId(1)));
}
#[test]
fn replace_reports_unwatched() {
let edges = Edges::new();
edges.replace(NodeId(2), vec![slot(1)]);
let became_unwatched = edges
.replace(NodeId(2), vec![])
.map(|diff| diff.became_unwatched);
assert_eq!(became_unwatched, Some(vec![NodeId(1)]));
}
#[test]
fn replace_with_reordered_parents_is_noop() {
let edges = Edges::new();
edges.replace(NodeId(3), vec![slot(1), slot(2)]);
assert!(edges.replace(NodeId(3), vec![slot(2), slot(1)]).is_none());
}
#[test]
fn replace_with_duplicate_parents_is_noop() {
let edges = Edges::new();
edges.replace(NodeId(3), vec![slot(1)]);
assert!(
edges
.replace(NodeId(3), vec![slot(1), slot(1), slot(1)])
.is_none()
);
}
#[test]
fn replace_with_different_parents_is_applied() {
let edges = Edges::new();
edges.replace(NodeId(3), vec![slot(1)]);
assert!(edges.replace(NodeId(3), vec![slot(2)]).is_some());
assert!(!edges.is_watched(NodeId(1)));
assert!(edges.is_watched(NodeId(2)));
}
#[test]
fn replace_diffs_only_what_changed() {
let edges = Edges::new();
edges.replace(NodeId(4), vec![slot(1), slot(2)]);
let diff = edges
.replace(NodeId(4), vec![slot(2), slot(3)])
.map(|diff| (diff.became_unwatched, diff.became_watched));
assert_eq!(diff, Some((vec![NodeId(1)], vec![NodeId(3)])));
assert!(!edges.is_watched(NodeId(1)));
assert!(edges.is_watched(NodeId(2)));
assert!(edges.is_watched(NodeId(3)));
}
#[test]
fn unregister_clears_parent_from_child() {
let edges = Edges::new();
edges.replace(NodeId(2), vec![slot(1)]);
edges.unregister(NodeId(1));
assert_eq!(edges.count_parents_if(NodeId(2), |_| true), 0);
}
#[test]
fn unregister_reports_unwatched() {
let edges = Edges::new();
edges.replace(NodeId(2), vec![slot(1)]);
assert_eq!(edges.unregister(NodeId(2)), vec![NodeId(1)]);
}
}