1use std::collections::VecDeque;
62use std::fmt;
63
64#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
72pub struct NodeId(u32);
73
74impl NodeId {
75 const NONE: u32 = u32::MAX;
77
78 #[must_use]
80 pub fn from_raw(index: u32) -> Self {
81 Self(index)
82 }
83
84 #[must_use]
86 pub fn raw(self) -> u32 {
87 self.0
88 }
89}
90
91impl fmt::Display for NodeId {
92 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
93 write!(f, "N{}", self.0)
94 }
95}
96
97#[derive(Debug, Clone, Copy, PartialEq, Eq)]
103pub enum InputKind {
104 Constraint,
106 Content,
108 Style,
110}
111
112#[derive(Clone)]
121struct DepNode {
122 generation: u32,
124 dirty_gen: u32,
127 constraint_hash: u64,
129 content_hash: u64,
131 style_hash: u64,
133 parent: u32,
135}
136
137impl DepNode {
138 fn new(generation: u32) -> Self {
139 Self {
140 generation,
141 dirty_gen: 0,
142 constraint_hash: 0,
143 content_hash: 0,
144 style_hash: 0,
145 parent: NodeId::NONE,
146 }
147 }
148
149 fn is_dirty(&self) -> bool {
150 self.dirty_gen >= self.generation
151 }
152}
153
154#[derive(Debug, Clone, PartialEq, Eq)]
160pub struct CycleError {
161 pub from: NodeId,
162 pub to: NodeId,
163}
164
165impl fmt::Display for CycleError {
166 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
167 write!(
168 f,
169 "layout cycle detected: {} → {} would create a cycle",
170 self.from, self.to
171 )
172 }
173}
174
175impl std::error::Error for CycleError {}
176
177pub struct DepGraph {
205 nodes: Vec<DepNode>,
206 fwd_adj: Vec<Vec<NodeId>>,
208 rev_adj: Vec<Vec<NodeId>>,
210 current_gen: u32,
212 pending_dirty: Vec<NodeId>,
214 free_list: Vec<u32>,
216}
217
218impl DepGraph {
219 #[must_use]
221 pub fn new() -> Self {
222 Self {
223 nodes: Vec::new(),
224 fwd_adj: Vec::new(),
225 rev_adj: Vec::new(),
226 current_gen: 1,
227 pending_dirty: Vec::new(),
228 free_list: Vec::new(),
229 }
230 }
231
232 #[must_use]
234 pub fn with_capacity(node_cap: usize, _edge_cap: usize) -> Self {
235 Self {
236 nodes: Vec::with_capacity(node_cap),
237 fwd_adj: Vec::with_capacity(node_cap),
238 rev_adj: Vec::with_capacity(node_cap),
239 current_gen: 1,
240 pending_dirty: Vec::new(),
241 free_list: Vec::new(),
242 }
243 }
244
245 pub fn add_node(&mut self) -> NodeId {
247 if let Some(slot) = self.free_list.pop() {
248 self.nodes[slot as usize] = DepNode::new(self.current_gen);
249 self.fwd_adj[slot as usize].clear();
250 self.rev_adj[slot as usize].clear();
251 NodeId(slot)
252 } else {
253 let id = self.nodes.len() as u32;
254 self.nodes.push(DepNode::new(self.current_gen));
255 self.fwd_adj.push(Vec::new());
256 self.rev_adj.push(Vec::new());
257 NodeId(id)
258 }
259 }
260
261 pub fn remove_node(&mut self, id: NodeId) {
263 let idx = id.0 as usize;
264 if idx < self.nodes.len() && self.nodes[idx].generation != 0 {
265 for &rev in &self.rev_adj[idx] {
267 let child_idx = rev.0 as usize;
268 if child_idx < self.nodes.len() && self.nodes[child_idx].parent == id.0 {
269 self.nodes[child_idx].parent = NodeId::NONE;
270 }
271 }
272
273 let fwds = std::mem::take(&mut self.fwd_adj[idx]);
275 for fwd in fwds {
276 if let Some(revs) = self.rev_adj.get_mut(fwd.0 as usize) {
277 revs.retain(|&x| x != id);
278 }
279 }
280
281 let revs = std::mem::take(&mut self.rev_adj[idx]);
283 for rev in revs {
284 if let Some(fwds) = self.fwd_adj.get_mut(rev.0 as usize) {
285 fwds.retain(|&x| x != id);
286 }
287 }
288
289 self.nodes[idx].generation = 0;
290 self.nodes[idx].dirty_gen = 0;
291 self.free_list.push(id.0);
292 }
293 }
294
295 #[must_use]
297 pub fn node_count(&self) -> usize {
298 self.nodes.len() - self.free_list.len()
299 }
300
301 #[must_use]
303 pub fn edge_count(&self) -> usize {
304 self.fwd_adj.iter().map(|v| v.len()).sum()
305 }
306
307 pub fn set_parent(&mut self, child: NodeId, parent: NodeId) {
309 if (child.0 as usize) < self.nodes.len() {
310 self.nodes[child.0 as usize].parent = parent.0;
311 }
312 }
313
314 #[must_use]
316 pub fn parent(&self, id: NodeId) -> Option<NodeId> {
317 let node = self.nodes.get(id.0 as usize)?;
318 if node.parent == NodeId::NONE {
319 None
320 } else {
321 Some(NodeId(node.parent))
322 }
323 }
324
325 pub fn add_edge(&mut self, from: NodeId, to: NodeId) -> Result<(), CycleError> {
329 if from == to {
331 return Err(CycleError { from, to });
332 }
333
334 if self.can_reach(to, from) {
337 return Err(CycleError { from, to });
338 }
339
340 self.fwd_adj[from.0 as usize].push(to);
342 self.rev_adj[to.0 as usize].push(from);
344
345 Ok(())
346 }
347
348 fn can_reach(&self, from: NodeId, to: NodeId) -> bool {
350 let mut visited = vec![false; self.nodes.len()];
351 let mut stack = vec![from];
352
353 while let Some(current) = stack.pop() {
354 if current == to {
355 return true;
356 }
357 let idx = current.0 as usize;
358 if idx >= self.nodes.len() || visited[idx] {
359 continue;
360 }
361 visited[idx] = true;
362
363 if self.nodes[idx].generation == 0 {
364 continue; }
366 for &dep in &self.fwd_adj[idx] {
367 if !visited[dep.0 as usize] {
368 stack.push(dep);
369 }
370 }
371 }
372 false
373 }
374
375 pub fn mark_changed(&mut self, id: NodeId, kind: InputKind, new_hash: u64) {
381 let idx = id.0 as usize;
382 if idx >= self.nodes.len() {
383 return;
384 }
385 let node = &mut self.nodes[idx];
386 if node.generation == 0 {
387 return; }
389
390 let old_hash = match kind {
391 InputKind::Constraint => &mut node.constraint_hash,
392 InputKind::Content => &mut node.content_hash,
393 InputKind::Style => &mut node.style_hash,
394 };
395
396 if *old_hash == new_hash {
397 return; }
399 *old_hash = new_hash;
400
401 node.dirty_gen = self.current_gen;
403 self.pending_dirty.push(id);
404 }
405
406 pub fn mark_dirty(&mut self, id: NodeId) {
408 let idx = id.0 as usize;
409 if idx >= self.nodes.len() {
410 return;
411 }
412 let node = &mut self.nodes[idx];
413 if node.generation == 0 {
414 return;
415 }
416 node.dirty_gen = self.current_gen;
417 self.pending_dirty.push(id);
418 }
419
420 pub fn propagate(&mut self) -> Vec<NodeId> {
426 if self.pending_dirty.is_empty() {
427 return Vec::new();
428 }
429
430 let mut queue = VecDeque::new();
432 let mut visited = vec![false; self.nodes.len()];
433
434 for &id in &self.pending_dirty {
435 let idx = id.0 as usize;
436 if idx < self.nodes.len() && !visited[idx] {
437 visited[idx] = true;
438 queue.push_back(id);
439 }
440 }
441 self.pending_dirty.clear();
442
443 while let Some(current) = queue.pop_front() {
444 let idx = current.0 as usize;
445 if self.nodes[idx].generation == 0 {
446 continue;
447 }
448
449 self.nodes[idx].dirty_gen = self.current_gen;
451
452 for i in 0..self.rev_adj[idx].len() {
454 let dependent = self.rev_adj[idx][i];
455 let dep_idx = dependent.0 as usize;
456 if dep_idx < self.nodes.len() && !visited[dep_idx] {
457 visited[dep_idx] = true;
458 queue.push_back(dependent);
459 }
460 }
461 }
462
463 self.collect_dirty_dfs_preorder()
465 }
466
467 fn collect_dirty_dfs_preorder(&self) -> Vec<NodeId> {
469 let mut roots = Vec::new();
471 for (i, node) in self.nodes.iter().enumerate() {
472 if node.generation == 0 || !node.is_dirty() {
473 continue;
474 }
475
476 let has_dirty_dependency = self.fwd_adj[i].iter().any(|&dep_id| {
477 let dep_idx = dep_id.0 as usize;
478 dep_idx < self.nodes.len()
479 && self.nodes[dep_idx].generation != 0
480 && self.nodes[dep_idx].is_dirty()
481 });
482
483 if !has_dirty_dependency {
484 roots.push(NodeId(i as u32));
485 }
486 }
487 roots.sort(); let mut result = Vec::new();
494 let mut visited = vec![false; self.nodes.len()];
495
496 for root in roots.into_iter().rev() {
497 self.dfs_postorder(root, &mut result, &mut visited);
498 }
499
500 result.reverse();
501 result
502 }
503
504 fn dfs_postorder(&self, id: NodeId, result: &mut Vec<NodeId>, visited: &mut [bool]) {
506 let idx = id.0 as usize;
507 if idx >= self.nodes.len() || visited[idx] {
508 return;
509 }
510 let node = &self.nodes[idx];
511 if node.generation == 0 || !node.is_dirty() {
512 return;
513 }
514 visited[idx] = true;
515
516 let mut children: Vec<NodeId> = self.rev_adj[idx]
518 .iter()
519 .filter(|d| {
520 let di = d.0 as usize;
521 di < self.nodes.len()
522 && !visited[di]
523 && self.nodes[di].generation != 0
524 && self.nodes[di].is_dirty()
525 })
526 .copied()
527 .collect();
528
529 children.sort(); for child in children.into_iter().rev() {
533 self.dfs_postorder(child, result, visited);
534 }
535
536 result.push(id);
537 }
538
539 #[must_use]
541 pub fn is_dirty(&self, id: NodeId) -> bool {
542 self.nodes
543 .get(id.0 as usize)
544 .is_some_and(|n| n.generation != 0 && n.is_dirty())
545 }
546
547 pub fn clean(&mut self, id: NodeId) {
549 if let Some(node) = self.nodes.get_mut(id.0 as usize) {
550 node.generation = self.current_gen;
551 node.dirty_gen = 0;
552 }
553 }
554
555 pub fn clean_all(&mut self) {
557 self.current_gen = self.current_gen.wrapping_add(1);
558 if self.current_gen == 0 {
559 self.current_gen = 1; }
561 for node in &mut self.nodes {
562 if node.generation != 0 {
563 node.generation = self.current_gen;
564 node.dirty_gen = 0;
565 }
566 }
567 self.pending_dirty.clear();
568 }
569
570 #[must_use]
572 pub fn constraint_hash(&self, id: NodeId) -> Option<u64> {
573 self.nodes
574 .get(id.0 as usize)
575 .filter(|n| n.generation != 0)
576 .map(|n| n.constraint_hash)
577 }
578
579 #[must_use]
581 pub fn content_hash(&self, id: NodeId) -> Option<u64> {
582 self.nodes
583 .get(id.0 as usize)
584 .filter(|n| n.generation != 0)
585 .map(|n| n.content_hash)
586 }
587
588 #[must_use]
590 pub fn style_hash(&self, id: NodeId) -> Option<u64> {
591 self.nodes
592 .get(id.0 as usize)
593 .filter(|n| n.generation != 0)
594 .map(|n| n.style_hash)
595 }
596
597 pub fn dirty_nodes(&self) -> impl Iterator<Item = NodeId> + '_ {
599 self.nodes
600 .iter()
601 .enumerate()
602 .filter(|(_, n)| n.generation != 0 && n.is_dirty())
603 .map(|(i, _)| NodeId(i as u32))
604 }
605
606 #[must_use]
608 pub fn dirty_count(&self) -> usize {
609 self.nodes
610 .iter()
611 .filter(|n| n.generation != 0 && n.is_dirty())
612 .count()
613 }
614
615 #[must_use]
617 pub fn dependencies(&self, id: NodeId) -> &[NodeId] {
618 let idx = id.0 as usize;
619 if idx >= self.nodes.len() || self.nodes[idx].generation == 0 {
620 return &[];
621 }
622 &self.fwd_adj[idx]
623 }
624
625 #[must_use]
627 pub fn dependents(&self, id: NodeId) -> &[NodeId] {
628 let idx = id.0 as usize;
629 if idx >= self.nodes.len() || self.nodes[idx].generation == 0 {
630 return &[];
631 }
632 &self.rev_adj[idx]
633 }
634
635 pub fn invalidate_all(&mut self) {
638 for (i, node) in self.nodes.iter_mut().enumerate() {
639 if node.generation != 0 {
640 node.dirty_gen = self.current_gen;
641 self.pending_dirty.push(NodeId(i as u32));
642 }
643 }
644 }
645}
646
647impl Default for DepGraph {
648 fn default() -> Self {
649 Self::new()
650 }
651}
652
653#[cfg(test)]
658mod tests {
659 use super::*;
660
661 #[test]
662 fn add_node_returns_sequential_ids() {
663 let mut g = DepGraph::new();
664 let a = g.add_node();
665 let b = g.add_node();
666 let c = g.add_node();
667 assert_eq!(a.raw(), 0);
668 assert_eq!(b.raw(), 1);
669 assert_eq!(c.raw(), 2);
670 assert_eq!(g.node_count(), 3);
671 }
672
673 #[test]
674 fn remove_node_recycles_slot() {
675 let mut g = DepGraph::new();
676 let a = g.add_node();
677 let _b = g.add_node();
678 g.remove_node(a);
679 assert_eq!(g.node_count(), 1);
680 let c = g.add_node();
681 assert_eq!(c.raw(), 0); assert_eq!(g.node_count(), 2);
683 }
684
685 #[test]
686 fn add_edge_creates_dependency() {
687 let mut g = DepGraph::new();
688 let a = g.add_node();
689 let b = g.add_node();
690 g.add_edge(a, b).unwrap();
691 assert_eq!(g.dependencies(a), &[b]);
692 assert_eq!(g.dependents(b), &[a]);
693 assert_eq!(g.edge_count(), 1);
694 }
695
696 #[test]
697 fn self_loop_detected() {
698 let mut g = DepGraph::new();
699 let a = g.add_node();
700 let err = g.add_edge(a, a).unwrap_err();
701 assert_eq!(err, CycleError { from: a, to: a });
702 }
703
704 #[test]
705 fn two_node_cycle_detected() {
706 let mut g = DepGraph::new();
707 let a = g.add_node();
708 let b = g.add_node();
709 g.add_edge(a, b).unwrap();
710 let err = g.add_edge(b, a).unwrap_err();
711 assert_eq!(err, CycleError { from: b, to: a });
712 }
713
714 #[test]
715 fn three_node_cycle_detected() {
716 let mut g = DepGraph::new();
717 let a = g.add_node();
718 let b = g.add_node();
719 let c = g.add_node();
720 g.add_edge(a, b).unwrap();
721 g.add_edge(b, c).unwrap();
722 let err = g.add_edge(c, a).unwrap_err();
723 assert_eq!(err, CycleError { from: c, to: a });
724 }
725
726 #[test]
727 fn dag_allows_diamond() {
728 let mut g = DepGraph::new();
730 let a = g.add_node();
731 let b = g.add_node();
732 let c = g.add_node();
733 let d = g.add_node();
734 g.add_edge(a, b).unwrap();
735 g.add_edge(a, c).unwrap();
736 g.add_edge(b, d).unwrap();
737 g.add_edge(c, d).unwrap();
738 assert_eq!(g.edge_count(), 4);
739 }
740
741 #[test]
742 fn mark_changed_deduplicates() {
743 let mut g = DepGraph::new();
744 let a = g.add_node();
745 g.mark_changed(a, InputKind::Constraint, 42);
746 assert!(g.is_dirty(a));
747
748 g.clean(a);
750 g.mark_changed(a, InputKind::Constraint, 42);
751 assert!(!g.is_dirty(a)); }
753
754 #[test]
755 fn mark_changed_different_hash() {
756 let mut g = DepGraph::new();
757 let a = g.add_node();
758 g.mark_changed(a, InputKind::Constraint, 42);
759 g.clean(a);
760 g.mark_changed(a, InputKind::Constraint, 99); assert!(g.is_dirty(a));
762 }
763
764 #[test]
765 fn propagate_single_node() {
766 let mut g = DepGraph::new();
767 let a = g.add_node();
768 g.mark_dirty(a);
769 let dirty = g.propagate();
770 assert_eq!(dirty, vec![a]);
771 }
772
773 #[test]
774 fn propagate_parent_to_child() {
775 let mut g = DepGraph::new();
776 let parent = g.add_node();
777 let child = g.add_node();
778 g.add_edge(child, parent).unwrap(); g.set_parent(child, parent);
780
781 g.mark_dirty(parent);
782 let dirty = g.propagate();
783 assert!(dirty.contains(&parent));
784 assert!(dirty.contains(&child));
785 }
786
787 #[test]
788 fn propagate_chain() {
789 let mut g = DepGraph::new();
791 let a = g.add_node();
792 let b = g.add_node();
793 let c = g.add_node();
794 g.add_edge(b, a).unwrap(); g.add_edge(c, b).unwrap(); g.set_parent(b, a);
797 g.set_parent(c, b);
798
799 g.mark_dirty(a);
800 let dirty = g.propagate();
801 assert_eq!(dirty.len(), 3);
802 assert!(dirty.contains(&a));
803 assert!(dirty.contains(&b));
804 assert!(dirty.contains(&c));
805 }
806
807 #[test]
808 fn propagate_only_affected_subtree() {
809 let mut g = DepGraph::new();
811 let a = g.add_node();
812 let b = g.add_node();
813 let c = g.add_node();
814 let d = g.add_node();
815 g.add_edge(b, a).unwrap();
816 g.add_edge(c, a).unwrap();
817
818 g.mark_dirty(a);
819 let dirty = g.propagate();
820 assert!(dirty.contains(&a));
821 assert!(dirty.contains(&b));
822 assert!(dirty.contains(&c));
823 assert!(!dirty.contains(&d));
824 }
825
826 #[test]
827 fn propagate_diamond_deduplicates() {
828 let mut g = DepGraph::new();
830 let a = g.add_node();
831 let b = g.add_node();
832 let c = g.add_node();
833 let d = g.add_node();
834 g.add_edge(b, a).unwrap();
835 g.add_edge(c, a).unwrap();
836 g.add_edge(d, b).unwrap();
837 g.add_edge(d, c).unwrap();
838
839 g.mark_dirty(a);
840 let dirty = g.propagate();
841 assert_eq!(dirty.len(), 4);
842 let unique: std::collections::HashSet<_> = dirty.iter().collect();
844 assert_eq!(unique.len(), 4);
845 }
846
847 #[test]
848 fn clean_all_resets() {
849 let mut g = DepGraph::new();
850 let a = g.add_node();
851 let b = g.add_node();
852 g.add_edge(b, a).unwrap();
853 g.mark_dirty(a);
854 g.propagate();
855 assert!(g.is_dirty(a));
856 assert!(g.is_dirty(b));
857
858 g.clean_all();
859 assert!(!g.is_dirty(a));
860 assert!(!g.is_dirty(b));
861 assert_eq!(g.dirty_count(), 0);
862 }
863
864 #[test]
865 fn invalidate_all_dirties_everything() {
866 let mut g = DepGraph::new();
867 let a = g.add_node();
868 let b = g.add_node();
869 let c = g.add_node();
870
871 g.invalidate_all();
872 let dirty = g.propagate();
873 assert_eq!(dirty.len(), 3);
874 assert!(dirty.contains(&a));
875 assert!(dirty.contains(&b));
876 assert!(dirty.contains(&c));
877 }
878
879 #[test]
880 fn parent_child_relationship() {
881 let mut g = DepGraph::new();
882 let a = g.add_node();
883 let b = g.add_node();
884 assert_eq!(g.parent(a), None);
885 g.set_parent(b, a);
886 assert_eq!(g.parent(b), Some(a));
887 }
888
889 #[test]
890 fn input_hashes_stored_independently() {
891 let mut g = DepGraph::new();
892 let a = g.add_node();
893 g.mark_changed(a, InputKind::Constraint, 1);
894 g.mark_changed(a, InputKind::Content, 2);
895 g.mark_changed(a, InputKind::Style, 3);
896
897 assert_eq!(g.constraint_hash(a), Some(1));
898 assert_eq!(g.content_hash(a), Some(2));
899 assert_eq!(g.style_hash(a), Some(3));
900 }
901
902 #[test]
903 fn dead_node_is_not_dirty() {
904 let mut g = DepGraph::new();
905 let a = g.add_node();
906 g.mark_dirty(a);
907 g.remove_node(a);
908 assert!(!g.is_dirty(a));
909 }
910
911 #[test]
912 fn propagate_skips_dead_nodes() {
913 let mut g = DepGraph::new();
914 let a = g.add_node();
915 let b = g.add_node();
916 let c = g.add_node();
917 g.add_edge(b, a).unwrap();
918 g.add_edge(c, b).unwrap();
919
920 g.remove_node(b);
922 g.mark_dirty(a);
923 let dirty = g.propagate();
924 assert!(dirty.contains(&a));
926 assert!(!dirty.contains(&c));
927 }
928
929 #[test]
930 fn propagate_empty_returns_empty() {
931 let mut g = DepGraph::new();
932 let _a = g.add_node();
933 let dirty = g.propagate();
934 assert!(dirty.is_empty());
935 }
936
937 #[test]
938 fn large_tree_propagation() {
939 let mut g = DepGraph::with_capacity(1000, 1000);
941 let root = g.add_node();
942 let mut leaves = Vec::new();
943
944 for _ in 0..10 {
945 let child = g.add_node();
946 g.add_edge(child, root).unwrap();
947 g.set_parent(child, root);
948
949 for _ in 0..10 {
950 let grandchild = g.add_node();
951 g.add_edge(grandchild, child).unwrap();
952 g.set_parent(grandchild, child);
953 leaves.push(grandchild);
954 }
955 }
956 assert_eq!(g.node_count(), 111); g.mark_dirty(root);
960 let dirty = g.propagate();
961 assert_eq!(dirty.len(), 111);
962
963 g.clean_all();
965 g.mark_dirty(leaves[42]);
966 let dirty = g.propagate();
967 assert_eq!(dirty.len(), 1);
968 assert_eq!(dirty[0], leaves[42]);
969 }
970
971 #[test]
972 fn node_size_under_64_bytes() {
973 assert!(
974 std::mem::size_of::<DepNode>() <= 64,
975 "DepNode is {} bytes, exceeds 64-byte budget",
976 std::mem::size_of::<DepNode>(),
977 );
978 }
979
980 #[test]
981 fn node_size_exactly_40_bytes() {
982 assert_eq!(
984 std::mem::size_of::<DepNode>(),
985 40,
986 "DepNode should be 40 bytes, got {}",
987 std::mem::size_of::<DepNode>(),
988 );
989 }
990
991 #[test]
992 fn multiple_input_changes_single_propagation() {
993 let mut g = DepGraph::new();
994 let a = g.add_node();
995 let b = g.add_node();
996 g.add_edge(b, a).unwrap();
997
998 g.mark_changed(a, InputKind::Constraint, 10);
1000 g.mark_changed(a, InputKind::Content, 20);
1001 let dirty = g.propagate();
1002 assert_eq!(dirty.len(), 2); }
1004
1005 #[test]
1006 fn propagate_dfs_preorder() {
1007 let mut g = DepGraph::new();
1009 let r = g.add_node(); let a = g.add_node(); let b = g.add_node(); let c = g.add_node(); g.add_edge(a, r).unwrap();
1014 g.add_edge(b, a).unwrap();
1015 g.add_edge(c, a).unwrap();
1016 g.set_parent(a, r);
1017 g.set_parent(b, a);
1018 g.set_parent(c, a);
1019
1020 g.mark_dirty(r);
1021 let dirty = g.propagate();
1022 assert_eq!(dirty.len(), 4);
1023 assert_eq!(dirty[0], r);
1025 assert_eq!(dirty[1], a);
1026 assert_eq!(dirty[2], b);
1028 assert_eq!(dirty[3], c);
1029 }
1030
1031 #[test]
1032 fn dirty_count_accurate() {
1033 let mut g = DepGraph::new();
1034 let a = g.add_node();
1035 let b = g.add_node();
1036 let c = g.add_node();
1037 assert_eq!(g.dirty_count(), 0);
1038
1039 g.mark_dirty(a);
1040 g.mark_dirty(b);
1041 g.propagate();
1042 assert_eq!(g.dirty_count(), 2);
1043
1044 g.clean(a);
1045 assert_eq!(g.dirty_count(), 1);
1046
1047 g.clean_all();
1048 assert_eq!(g.dirty_count(), 0);
1049
1050 let _ = c;
1052 }
1053
1054 #[test]
1055 fn dependencies_and_dependents_api() {
1056 let mut g = DepGraph::new();
1057 let a = g.add_node();
1058 let b = g.add_node();
1059 let c = g.add_node();
1060 g.add_edge(b, a).unwrap(); g.add_edge(c, a).unwrap(); assert_eq!(g.dependencies(b), &[a]);
1064 assert_eq!(g.dependencies(c), &[a]);
1065 assert_eq!(g.dependents(a).len(), 2);
1066 }
1067}