Skip to main content

unlit_wgpu/
dag.rs

1//! A directed acyclic graph with slot handles.
2//!
3//! This is the structure [`ResourceGraph`](crate::resources::ResourceGraph)
4//! keeps its resources in. It is a directed graph plus one rule the rest of the
5//! design leans on: an edge that would close a cycle is refused, so the nodes
6//! can always be visited in dependency order.
7//!
8//! # Handles
9//!
10//! A [`NodeId`] names the slot a node occupies together with the generation
11//! that slot was in when the node was inserted. Removing a node frees its slot
12//! for a later insertion, and that insertion bumps the slot's generation, so a
13//! handle to a removed node never resolves again: it reports "no such node"
14//! rather than naming whatever took the slot. Handles can therefore travel
15//! through an API that stores only bits — see [`NodeId::to_bits`] — and be
16//! resolved back with [`Dag::get`], with a stale handle failing loudly instead
17//! of silently rebinding. Deciding liveness is still the caller's job, not the
18//! graph's: [`ResHandle`](crate::resources::ResHandle) carries a strong
19//! reference that [`ResourceGraph`](crate::resources::ResourceGraph) collects
20//! on, so a node whose handle is still held is never removed in the first
21//! place.
22//!
23//! # Edges
24//!
25//! An edge points from a node to a node that depends on it: a dependency, then
26//! the resource built from it. [`Dag::dependencies`] therefore walks back along
27//! the edges, [`Dag::for_each_dependent_mut`] walks forward, and
28//! [`Dag::topological_order`] puts every node after the nodes it depends on. An
29//! edge is recorded once however often it is declared, which
30//! [`Dag::add_edge`] reports: only the declaration that recorded it returns
31//! `true`.
32
33use std::vec::Vec;
34
35/// The slot a node occupies in a [`Dag`], with the generation it was in.
36///
37/// A handle is only valid while the node it names lives: once the node is
38/// removed and its slot is reused, the generation no longer matches and the
39/// handle stops resolving (see the [module documentation](self#handles)).
40#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
41pub struct NodeId {
42    /// The slot the node occupies.
43    index: u32,
44    /// The generation the slot was in when the node was inserted. A reused
45    /// slot carries a higher generation, so old handles do not resolve.
46    generation: u32,
47}
48
49impl NodeId {
50    /// The slot this handle names.
51    ///
52    /// Only meaningful while the node lives, but stable across the node's
53    /// lifetime, so callers can group or sort by it without holding a borrow.
54    /// Two handles can share an index and still differ: the generation is what
55    /// tells a live handle from a stale one.
56    #[must_use]
57    pub const fn index(self) -> usize {
58        self.index as usize
59    }
60
61    /// The handle as plain bits, for an API that stores handles but does not
62    /// know the type — an inspector that reports one, or a transport that
63    /// carries it.
64    ///
65    /// The slot index is the low 32 bits and the generation the high 32, the
66    /// same layout as `unlit_ecs::Entity`. Rebuild the handle with
67    /// [`NodeId::from_bits`].
68    #[must_use]
69    pub const fn to_bits(self) -> u64 {
70        ((self.generation as u64) << 32) | self.index as u64
71    }
72
73    /// Rebuild a handle from the bits [`NodeId::to_bits`] produced.
74    ///
75    /// The bits say nothing about whether the node still lives; resolving them
76    /// through the graph is what decides that.
77    #[must_use]
78    pub const fn from_bits(bits: u64) -> Self {
79        Self {
80            index: bits as u32,
81            generation: (bits >> 32) as u32,
82        }
83    }
84}
85
86/// Why [`Dag::add_edge`] refused an edge.
87#[derive(Clone, Copy, PartialEq, Eq, Debug)]
88pub enum EdgeError {
89    /// The endpoint does not resolve: its node was removed, or the handle never
90    /// came from this graph.
91    NoSuchNode(NodeId),
92    /// Both endpoints are the same node.
93    SelfLoop,
94    /// The edge would close a cycle.
95    Cycle,
96}
97
98/// A slot a node occupies.
99#[derive(Debug)]
100struct NodeSlot<N> {
101    /// The node's weight, or `None` while the slot is vacant.
102    weight: Option<N>,
103    /// The generation this slot is in. Bumped whenever a node is removed and
104    /// the slot is handed out again, so handles from the previous occupant
105    /// stop resolving.
106    generation: u32,
107    /// Edges leaving this node, towards the nodes that depend on it.
108    dependents: Vec<u32>,
109    /// Edges entering this node, from the nodes it depends on.
110    dependencies: Vec<u32>,
111}
112
113/// An edge, pointing from a dependency to what depends on it.
114#[derive(Debug)]
115struct EdgeSlot {
116    /// The dependency.
117    from: u32,
118    /// The node built from it.
119    to: u32,
120}
121
122/// A directed acyclic graph whose edges may not close a cycle.
123///
124/// Slots are recycled: removing a node frees its slot, and a later insertion
125/// reuses it. Neither the node array nor the walk stamps shrink, so a graph that
126/// once held many nodes keeps scanning that many: memory is bounded by the peak
127/// number of nodes held at once, not by the number held now.
128#[derive(Debug)]
129pub struct Dag<N> {
130    nodes: Vec<NodeSlot<N>>,
131    edges: Vec<EdgeSlot>,
132    /// Slots holding no node, to be handed out by [`Dag::insert`].
133    free_nodes: Vec<u32>,
134    /// Edge slots holding no edge, to be handed out by [`Dag::add_edge`].
135    free_edges: Vec<u32>,
136    /// How many slots hold a node.
137    live: usize,
138    /// Per-slot stamp of the most recent walk that visited it. A walk takes a
139    /// fresh stamp rather than clearing this, so a traversal after the array
140    /// has grown allocates nothing.
141    visited: Vec<u32>,
142    /// The stamp `visited` presently records.
143    stamp: u32,
144    /// Reused stack of slot indices for the plain depth-first walks.
145    stack: Vec<u32>,
146    /// Reused stack for the post-order walk, where a slot is pushed once to be
147    /// visited and once more — flagged `true` — to be emitted.
148    plan_stack: Vec<(u32, bool)>,
149    /// Reused list of the slots a plan selected, in the order it selected them.
150    order: Vec<u32>,
151}
152
153impl<N> Default for Dag<N> {
154    fn default() -> Self {
155        Self::new()
156    }
157}
158
159impl<N> Dag<N> {
160    /// Create an empty graph.
161    pub fn new() -> Self {
162        Self {
163            nodes: Vec::new(),
164            edges: Vec::new(),
165            free_nodes: Vec::new(),
166            free_edges: Vec::new(),
167            live: 0,
168            visited: Vec::new(),
169            stamp: 0,
170            stack: Vec::new(),
171            plan_stack: Vec::new(),
172            order: Vec::new(),
173        }
174    }
175
176    /// Number of nodes in the graph.
177    pub fn len(&self) -> usize {
178        self.live
179    }
180
181    /// Whether the graph holds no nodes.
182    pub fn is_empty(&self) -> bool {
183        self.live == 0
184    }
185
186    /// Add `weight` as a node and return a handle to it.
187    ///
188    /// Reusing a freed slot bumps its generation, so handles to the node that
189    /// occupied the slot before stop resolving.
190    pub fn insert(&mut self, weight: N) -> NodeId {
191        let index = match self.free_nodes.pop() {
192            Some(index) => {
193                let slot = &mut self.nodes[index as usize];
194                debug_assert!(slot.weight.is_none(), "a free slot holds no node");
195                slot.weight = Some(weight);
196                slot.generation = slot.generation.wrapping_add(1);
197                slot.dependents.clear();
198                slot.dependencies.clear();
199                index
200            }
201            None => {
202                self.nodes.push(NodeSlot {
203                    weight: Some(weight),
204                    generation: 0,
205                    dependents: Vec::new(),
206                    dependencies: Vec::new(),
207                });
208                (self.nodes.len() - 1) as u32
209            }
210        };
211        self.live += 1;
212        self.node_id(index)
213    }
214
215    /// Every live node, as its handle and its weight, in slot order.
216    ///
217    /// Slots are recycled, so the order is by slot rather than by insertion: a
218    /// node that reused a freed slot appears where that slot is. The walk
219    /// covers the whole node array, so it costs the peak number of nodes the
220    /// graph ever held rather than the number it holds now.
221    pub fn iter(&self) -> impl Iterator<Item = (NodeId, &N)> {
222        self.nodes.iter().enumerate().filter_map(|(index, slot)| {
223            slot.weight
224                .as_ref()
225                .map(|weight| (self.node_id(index as u32), weight))
226        })
227    }
228
229    /// Borrow the weight of the node `id` names.
230    pub fn get(&self, id: NodeId) -> Option<&N> {
231        let index = self.resolve(id)?;
232        self.nodes[index].weight.as_ref()
233    }
234
235    /// Mutably borrow the weight of the node `id` names.
236    pub fn get_mut(&mut self, id: NodeId) -> Option<&mut N> {
237        let index = self.resolve(id)?;
238        self.nodes[index].weight.as_mut()
239    }
240
241    /// Record that `to` depends on `from`, and report whether this call
242    /// recorded it.
243    ///
244    /// Refused when either handle does not resolve, when both name the same
245    /// node, or when the edge would close a cycle. Declaring an existing
246    /// dependency again is a no-op rather than a second edge, so
247    /// [`Dag::dependencies`] never yields the same node twice; such a repeat
248    /// returns `Ok(false)`, and only the call that recorded the edge returns
249    /// `Ok(true)`.
250    pub fn add_edge(&mut self, from: NodeId, to: NodeId) -> Result<bool, EdgeError> {
251        let Some(from_index) = self.resolve(from) else {
252            return Err(EdgeError::NoSuchNode(from));
253        };
254        let Some(to_index) = self.resolve(to) else {
255            return Err(EdgeError::NoSuchNode(to));
256        };
257        if from_index == to_index {
258            return Err(EdgeError::SelfLoop);
259        }
260        // `from -> to` closes a cycle exactly when `to` already reaches `from`.
261        if self.reaches(to_index, from_index) {
262            return Err(EdgeError::Cycle);
263        }
264        if self.has_edge(from_index, to_index) {
265            return Ok(false);
266        }
267        let edge = match self.free_edges.pop() {
268            Some(edge) => {
269                self.edges[edge as usize] = EdgeSlot {
270                    from: from_index as u32,
271                    to: to_index as u32,
272                };
273                edge
274            }
275            None => {
276                self.edges.push(EdgeSlot {
277                    from: from_index as u32,
278                    to: to_index as u32,
279                });
280                (self.edges.len() - 1) as u32
281            }
282        };
283        self.nodes[from_index].dependents.push(edge);
284        self.nodes[to_index].dependencies.push(edge);
285        Ok(true)
286    }
287
288    /// The nodes `id` depends on, in unspecified order.
289    ///
290    /// Empty when the handle does not resolve.
291    pub fn dependencies(&self, id: NodeId) -> impl Iterator<Item = NodeId> + '_ {
292        self.incident(id, |slot| &slot.dependencies)
293            .map(|edge| self.node_id(self.edges[edge as usize].from))
294    }
295
296    /// Apply `f` to `id` and to every node reachable from it by following
297    /// dependents.
298    ///
299    /// Only the reachable nodes are visited. A handle that does not resolve
300    /// visits nothing.
301    pub fn for_each_dependent_mut(&mut self, id: NodeId, mut f: impl FnMut(&mut N)) {
302        self.plan_dependents(id);
303        let order = core::mem::take(&mut self.order);
304        for &index in &order {
305            if let Some(weight) = self.nodes[index as usize].weight.as_mut() {
306                f(weight);
307            }
308        }
309        self.order = order;
310    }
311
312    /// Remove every node `is_dead` reports, and return how many were removed.
313    ///
314    /// A node is dropped with the weights of the nodes that depend on it still
315    /// in place, so a weight whose drop releases something another node was
316    /// waiting for only shows up on the next call: a caller that has to reach a
317    /// fixpoint sweeps until this reports `0`.
318    pub fn remove_where_drop(&mut self, is_dead: impl Fn(&N) -> bool) -> usize {
319        self.order.clear();
320        self.order.extend(
321            (0..self.nodes.len())
322                .filter(|&index| self.nodes[index].weight.as_ref().is_some_and(&is_dead))
323                .map(|index| index as u32),
324        );
325        let order = core::mem::take(&mut self.order);
326        let removed = order.len();
327        for &index in &order {
328            let id = self.node_id(index);
329            self.remove(id);
330        }
331        self.order = order;
332        removed
333    }
334
335    /// Every node, ordered so that a node always follows the nodes it depends
336    /// on.
337    ///
338    /// Nodes that do not depend on one another come in an unspecified order.
339    /// The graph refuses every edge that would close a cycle, so every node is
340    /// listed.
341    pub fn topological_order(&self) -> Vec<NodeId> {
342        // How many dependencies each node is still waiting for, and the nodes
343        // waiting for none.
344        let mut pending: Vec<u32> = self
345            .nodes
346            .iter()
347            .map(|slot| {
348                if slot.weight.is_some() {
349                    slot.dependencies.len() as u32
350                } else {
351                    0
352                }
353            })
354            .collect();
355        let mut ready: Vec<u32> = self
356            .nodes
357            .iter()
358            .enumerate()
359            .filter_map(|(index, slot)| {
360                (slot.weight.is_some() && slot.dependencies.is_empty()).then_some(index as u32)
361            })
362            .collect();
363        let mut order = Vec::with_capacity(self.live);
364        while let Some(index) = ready.pop() {
365            order.push(self.node_id(index));
366            for &edge in &self.nodes[index as usize].dependents {
367                let to = self.edges[edge as usize].to;
368                pending[to as usize] -= 1;
369                if pending[to as usize] == 0 {
370                    ready.push(to);
371                }
372            }
373        }
374        debug_assert_eq!(order.len(), self.live, "the graph is acyclic");
375        order
376    }
377
378    /// Remove the node `id` names together with every edge touching it, and
379    /// return its weight.
380    ///
381    /// Internal: callers go through [`Dag::remove_where_drop`], which selects
382    /// what to remove without needing a handle the caller may no longer hold.
383    fn remove(&mut self, id: NodeId) -> Option<N> {
384        let index = self.resolve(id)?;
385        let slot = &mut self.nodes[index];
386        let weight = slot.weight.take()?;
387        // The incident edges are unlinked from the node at their far end, and
388        // this slot's own lists are emptied for the insertion that reuses it.
389        let dependents = core::mem::take(&mut slot.dependents);
390        let dependencies = core::mem::take(&mut slot.dependencies);
391        self.live -= 1;
392        self.free_nodes.push(index as u32);
393        for edge in dependents {
394            let to = self.edges[edge as usize].to;
395            self.nodes[to as usize].dependencies.retain(|&e| e != edge);
396            self.free_edges.push(edge);
397        }
398        for edge in dependencies {
399            let from = self.edges[edge as usize].from;
400            self.nodes[from as usize].dependents.retain(|&e| e != edge);
401            self.free_edges.push(edge);
402        }
403        Some(weight)
404    }
405
406    /// Resolve a handle to a slot index, or `None` when it names no node.
407    ///
408    /// A handle names both a slot and the generation it was in, so a handle
409    /// whose node was removed — and whose slot was perhaps reused — resolves to
410    /// nothing rather than to the slot's current occupant.
411    fn resolve(&self, id: NodeId) -> Option<usize> {
412        let index = id.index as usize;
413        self.nodes
414            .get(index)
415            .filter(|slot| slot.weight.is_some() && slot.generation == id.generation)
416            .map(|_| index)
417    }
418
419    /// Build the handle for a live slot, in the generation the slot is in.
420    fn node_id(&self, index: u32) -> NodeId {
421        NodeId {
422            index,
423            generation: self.nodes[index as usize].generation,
424        }
425    }
426
427    /// Whether `to` depends on `from`, both already resolved to slot indices.
428    fn has_edge(&self, from: usize, to: usize) -> bool {
429        self.nodes[from]
430            .dependents
431            .iter()
432            .any(|&edge| self.edges[edge as usize].to as usize == to)
433    }
434
435    /// The edge list `slot` selects, or an empty one when `id` does not
436    /// resolve.
437    fn incident<'a>(
438        &'a self,
439        id: NodeId,
440        select: impl Fn(&'a NodeSlot<N>) -> &'a Vec<u32>,
441    ) -> impl Iterator<Item = u32> + 'a {
442        self.resolve(id)
443            .map_or(&[][..], |index| select(&self.nodes[index]).as_slice())
444            .iter()
445            .copied()
446    }
447
448    /// Whether following dependents from `start` reaches `target`.
449    fn reaches(&mut self, start: usize, target: usize) -> bool {
450        if start == target {
451            return true;
452        }
453        let stamp = self.next_stamp();
454        let mut stack = core::mem::take(&mut self.stack);
455        stack.clear();
456        stack.push(start as u32);
457        self.visited[start] = stamp;
458        let mut found = false;
459        while let Some(index) = stack.pop() {
460            for &edge in &self.nodes[index as usize].dependents {
461                let to = self.edges[edge as usize].to as usize;
462                if to == target {
463                    found = true;
464                    break;
465                }
466                if self.visited[to] != stamp {
467                    self.visited[to] = stamp;
468                    stack.push(to as u32);
469                }
470            }
471            if found {
472                break;
473            }
474        }
475        self.stack = stack;
476        found
477    }
478
479    /// Fill [`Dag::order`] with `root` and every node reachable from it by
480    /// following dependents, in dependency order.
481    ///
482    /// The list is built before the caller acts on it, so a weight mutated as
483    /// the walk proceeds cannot disturb it.
484    fn plan_dependents(&mut self, root: NodeId) {
485        self.order.clear();
486        let Some(root_index) = self.resolve(root) else {
487            return;
488        };
489        // Post-order: a slot is pushed twice — once to visit it and once to
490        // emit it — so it is emitted only after everything reachable from it.
491        let stamp = self.next_stamp();
492        let mut stack = core::mem::take(&mut self.plan_stack);
493        let mut order = core::mem::take(&mut self.order);
494        stack.clear();
495        stack.push((root_index as u32, false));
496        self.visited[root_index] = stamp;
497        while let Some((index, expanded)) = stack.pop() {
498            if expanded {
499                order.push(index);
500                continue;
501            }
502            stack.push((index, true));
503            for &edge in &self.nodes[index as usize].dependents {
504                let to = self.edges[edge as usize].to;
505                if self.visited[to as usize] != stamp {
506                    self.visited[to as usize] = stamp;
507                    stack.push((to, false));
508                }
509            }
510        }
511        self.plan_stack = stack;
512        // Post-order emits a node after everything reachable from it, so
513        // reversing puts dependencies first — the order a removal returns.
514        order.reverse();
515        self.order = order;
516    }
517
518    /// Take a stamp that no mark in `visited` carries yet, growing the array to
519    /// cover the nodes if needed.
520    fn next_stamp(&mut self) -> u32 {
521        if self.visited.len() < self.nodes.len() {
522            self.visited.resize(self.nodes.len(), 0);
523        }
524        self.stamp = self.stamp.wrapping_add(1);
525        if self.stamp == 0 {
526            // The stamps wrapped, so every mark is stale: clear them and start
527            // again. Reached once every four billion walks.
528            self.visited.fill(0);
529            self.stamp = 1;
530        }
531        self.stamp
532    }
533}
534
535#[cfg(test)]
536mod tests {
537    use super::*;
538
539    #[test]
540    fn a_removed_slot_is_reused_with_a_new_generation() {
541        let mut dag = Dag::new();
542        let first = dag.insert("first");
543        assert_eq!(first.index(), 0);
544        assert_eq!(first.to_bits(), 0, "generation 0 in the high bits");
545        assert_eq!(dag.get(first), Some(&"first"));
546
547        assert_eq!(dag.remove(first), Some("first"));
548        assert_eq!(dag.get(first), None, "the slot is vacant");
549
550        // The slot is reused, but the generation moves on, so the old handle
551        // never comes to name the node that took its place.
552        let second = dag.insert("second");
553        assert_eq!(second.index(), first.index(), "the slot is reused");
554        assert_ne!(second.to_bits(), first.to_bits(), "but not the handle");
555        assert_eq!(dag.get(second), Some(&"second"));
556        assert_eq!(dag.get(first), None, "the old handle is stale");
557    }
558
559    #[test]
560    fn a_handle_survives_a_round_trip_through_bits() {
561        let mut dag = Dag::new();
562        let id = dag.insert("value");
563        let bits = id.to_bits();
564        assert_eq!(NodeId::from_bits(bits), id);
565        assert_eq!(dag.get(NodeId::from_bits(bits)), Some(&"value"));
566
567        // The bits carry the generation, so reusing the slot does not make the
568        // old bits name the new node.
569        dag.remove(id);
570        let reused = dag.insert("reused");
571        assert_eq!(reused.index(), id.index(), "the slot is reused");
572        assert_eq!(
573            dag.get(NodeId::from_bits(bits)),
574            None,
575            "the bits of a removed node do not resolve"
576        );
577    }
578
579    #[test]
580    fn removing_a_node_removes_only_its_edges() {
581        let mut dag = Dag::new();
582        let base = dag.insert("base");
583        let middle = dag.insert("middle");
584        let leaf = dag.insert("leaf");
585        dag.add_edge(base, middle).unwrap();
586        dag.add_edge(middle, leaf).unwrap();
587
588        dag.remove(middle);
589        assert_eq!(dag.dependencies(base).count(), 0);
590        assert_eq!(
591            dag.dependencies(leaf).count(),
592            0,
593            "the far end of the edge was unlinked too"
594        );
595        assert_eq!(dag.len(), 2);
596    }
597
598    #[test]
599    fn an_edge_that_would_close_a_cycle_is_refused() {
600        let mut dag = Dag::new();
601        let a = dag.insert("a");
602        let b = dag.insert("b");
603        let c = dag.insert("c");
604        dag.add_edge(a, b).unwrap();
605        dag.add_edge(b, c).unwrap();
606
607        assert_eq!(dag.add_edge(c, a), Err(EdgeError::Cycle));
608        assert_eq!(dag.add_edge(a, a), Err(EdgeError::SelfLoop));
609        assert_eq!(dag.len(), 3, "nothing was removed");
610        assert_eq!(
611            dag.dependencies(b).collect::<Vec<_>>(),
612            vec![a],
613            "the refused edges were not recorded"
614        );
615    }
616
617    #[test]
618    fn declaring_the_same_edge_twice_records_it_once() {
619        let mut dag = Dag::new();
620        let base = dag.insert("base");
621        let dependent = dag.insert("dependent");
622        dag.add_edge(base, dependent).unwrap();
623        dag.add_edge(base, dependent).unwrap();
624
625        assert_eq!(dag.dependencies(dependent).collect::<Vec<_>>(), vec![base]);
626    }
627
628    #[test]
629    fn an_edge_with_a_stale_endpoint_names_the_node_it_refused() {
630        let mut dag = Dag::new();
631        let base = dag.insert("base");
632        let removed = dag.insert("removed");
633        dag.remove(removed);
634
635        assert_eq!(
636            dag.add_edge(base, removed),
637            Err(EdgeError::NoSuchNode(removed))
638        );
639    }
640
641    #[test]
642    fn the_topological_order_puts_every_dependency_first() {
643        let mut dag = Dag::new();
644        //     base
645        //    /    \
646        //  left   right
647        //    \    /
648        //     join
649        let base = dag.insert("base");
650        let left = dag.insert("left");
651        let right = dag.insert("right");
652        let join = dag.insert("join");
653        dag.add_edge(base, left).unwrap();
654        dag.add_edge(base, right).unwrap();
655        dag.add_edge(left, join).unwrap();
656        dag.add_edge(right, join).unwrap();
657
658        let order = dag.topological_order();
659        let position = |id: NodeId| order.iter().position(|&node| node == id).unwrap();
660        assert_eq!(order.len(), 4);
661        assert!(position(base) < position(left));
662        assert!(position(base) < position(right));
663        assert!(position(left) < position(join));
664        assert!(position(right) < position(join));
665    }
666
667    #[test]
668    fn the_topological_order_lists_isolated_nodes() {
669        let mut dag = Dag::new();
670        dag.insert("a");
671        dag.insert("b");
672        assert_eq!(dag.topological_order().len(), 2);
673    }
674
675    #[test]
676    fn applying_to_dependents_visits_the_subtree_and_nothing_else() {
677        let mut dag = Dag::new();
678        let base = dag.insert(0u32);
679        let middle = dag.insert(0);
680        let leaf = dag.insert(0);
681        let unrelated = dag.insert(0);
682        dag.add_edge(base, middle).unwrap();
683        dag.add_edge(middle, leaf).unwrap();
684
685        dag.for_each_dependent_mut(middle, |count| *count += 1);
686        assert_eq!(dag.get(base), Some(&0), "the walk only goes forwards");
687        assert_eq!(dag.get(middle), Some(&1));
688        assert_eq!(dag.get(leaf), Some(&1));
689        assert_eq!(dag.get(unrelated), Some(&0));
690    }
691
692    #[test]
693    fn removing_where_leaves_the_nodes_that_were_not_selected() {
694        let mut dag = Dag::new();
695        let base = dag.insert("base");
696        let middle = dag.insert("middle");
697        let root = dag.insert("root");
698        let outside = dag.insert("outside");
699        dag.add_edge(base, middle).unwrap();
700        dag.add_edge(middle, root).unwrap();
701
702        let removed = dag.remove_where_drop(|&name| name == "middle");
703        assert_eq!(removed, 1);
704        assert_eq!(dag.get(base), Some(&"base"), "the node it depends on stays");
705        assert_eq!(dag.get(outside), Some(&"outside"));
706        assert_eq!(dag.get(middle), None);
707        assert_eq!(
708            dag.get(root),
709            Some(&"root"),
710            "a dependent is not removed with its dependency"
711        );
712        assert_eq!(dag.len(), 3);
713    }
714
715    /// A sweep selects from the weights present when it starts, so a weight
716    /// whose drop changes what is collectable is only seen by the next call.
717    #[test]
718    fn removing_where_reports_the_count_so_a_caller_can_sweep_to_a_fixpoint() {
719        let mut dag = Dag::new();
720        // `first` and `second` are both selected; `third` is not, but the
721        // closure counts how many sweeps it takes to reach one that removes
722        // nothing.
723        dag.insert("first");
724        dag.insert("second");
725        dag.insert("keep");
726        let mut sweeps = 0;
727        loop {
728            sweeps += 1;
729            let removed = dag.remove_where_drop(|&name| name != "keep");
730            if removed == 0 {
731                break;
732            }
733        }
734
735        assert_eq!(sweeps, 2, "one sweep removed the two, the next found none");
736        assert_eq!(dag.len(), 1);
737    }
738}