| //! A graph-like structure used to represent a set of dependencies and in what |
| //! order they should be built. |
| //! |
| //! This structure is used to store the dependency graph and dynamically update |
| //! it to figure out when a dependency should be built. |
| //! |
| //! Dependencies in this queue are represented as a (node, edge) pair. This is |
| //! used to model nodes which produce multiple outputs at different times but |
| //! some nodes may only require one of the outputs and can start before the |
| //! whole node is finished. |
| |
| use crate::util::data_structures::{HashMap, HashSet}; |
| use std::hash::Hash; |
| |
| #[derive(Debug)] |
| pub struct DependencyQueue<N: Hash + Eq, E: Hash + Eq, V> { |
| /// A list of all known keys to build. |
| /// |
| /// The value of the hash map is list of dependencies which still need to be |
| /// built before the package can be built. Note that the set is dynamically |
| /// updated as more dependencies are built. |
| dep_map: HashMap<N, (HashSet<(N, E)>, V)>, |
| |
| /// A reverse mapping of a package to all packages that depend on that |
| /// package. |
| /// |
| /// This map is statically known and does not get updated throughout the |
| /// lifecycle of the `DependencyQueue`. |
| /// |
| /// This is sort of like a `HashMap<(N, E), HashSet<N>>` map, but more |
| /// easily indexable with just an `N` |
| reverse_dep_map: HashMap<N, HashMap<E, HashSet<N>>>, |
| |
| /// The relative priority of this package. Higher values should be scheduled sooner. |
| priority: HashMap<N, usize>, |
| |
| /// An expected cost for building this package. Used to determine priority. |
| cost: HashMap<N, usize>, |
| } |
| |
| impl<N: Hash + Eq, E: Hash + Eq, V> Default for DependencyQueue<N, E, V> { |
| fn default() -> DependencyQueue<N, E, V> { |
| DependencyQueue::new() |
| } |
| } |
| |
| impl<N: Hash + Eq, E: Hash + Eq, V> DependencyQueue<N, E, V> { |
| /// Creates a new dependency queue with 0 packages. |
| pub fn new() -> DependencyQueue<N, E, V> { |
| DependencyQueue { |
| dep_map: HashMap::default(), |
| reverse_dep_map: HashMap::default(), |
| priority: HashMap::default(), |
| cost: HashMap::default(), |
| } |
| } |
| } |
| |
| impl<N: Hash + Eq + Clone, E: Eq + Hash + Clone, V> DependencyQueue<N, E, V> { |
| /// Adds a new node and its dependencies to this queue. |
| /// |
| /// The `key` specified is a new node in the dependency graph, and the node |
| /// depend on all the dependencies iterated by `dependencies`. Each |
| /// dependency is a node/edge pair, where edges can be thought of as |
| /// productions from nodes (aka if it's just `()` it's just waiting for the |
| /// node to finish). |
| /// |
| /// An optional `value` can also be associated with `key` which is reclaimed |
| /// when the node is ready to go. |
| /// |
| /// The cost parameter can be used to hint at the relative cost of building |
| /// this node. This implementation does not care about the units of this value, so |
| /// the calling code is free to use whatever they'd like. In general, higher cost |
| /// nodes are expected to take longer to build. |
| pub fn queue( |
| &mut self, |
| key: N, |
| value: V, |
| dependencies: impl IntoIterator<Item = (N, E)>, |
| cost: usize, |
| ) { |
| assert!(!self.dep_map.contains_key(&key)); |
| |
| let mut my_dependencies = HashSet::default(); |
| for (dep, edge) in dependencies { |
| my_dependencies.insert((dep.clone(), edge.clone())); |
| self.reverse_dep_map |
| .entry(dep) |
| .or_insert_with(HashMap::default) |
| .entry(edge) |
| .or_insert_with(HashSet::default) |
| .insert(key.clone()); |
| } |
| self.dep_map.insert(key.clone(), (my_dependencies, value)); |
| self.cost.insert(key, cost); |
| } |
| |
| /// All nodes have been added, calculate some internal metadata and prepare |
| /// for `dequeue`. |
| pub fn queue_finished(&mut self) { |
| let mut out = HashMap::default(); |
| for key in self.dep_map.keys() { |
| depth(key, &self.reverse_dep_map, &mut out); |
| } |
| self.priority = out |
| .into_iter() |
| .map(|(n, set)| { |
| let total_cost = |
| self.cost[&n] + set.iter().map(|key| self.cost[key]).sum::<usize>(); |
| (n, total_cost) |
| }) |
| .collect(); |
| |
| /// Creates a flattened reverse dependency list. For a given key, finds the |
| /// set of nodes which depend on it, including transitively. This is different |
| /// from `self.reverse_dep_map` because `self.reverse_dep_map` only maps one level |
| /// of reverse dependencies. |
| fn depth<'a, N: Hash + Eq + Clone, E: Hash + Eq + Clone>( |
| key: &N, |
| map: &HashMap<N, HashMap<E, HashSet<N>>>, |
| results: &'a mut HashMap<N, HashSet<N>>, |
| ) -> &'a HashSet<N> { |
| if results.contains_key(key) { |
| let depth = &results[key]; |
| assert!(!depth.is_empty(), "cycle in DependencyQueue"); |
| return depth; |
| } |
| results.insert(key.clone(), HashSet::default()); |
| |
| let mut set = HashSet::default(); |
| set.insert(key.clone()); |
| |
| for dep in map |
| .get(key) |
| .into_iter() |
| .flat_map(|it| it.values()) |
| .flatten() |
| { |
| set.extend(depth(dep, map, results).iter().cloned()) |
| } |
| |
| let slot = results.get_mut(key).unwrap(); |
| *slot = set; |
| &*slot |
| } |
| } |
| |
| /// Dequeues a package that is ready to be built. |
| /// |
| /// A package is ready to be built when it has 0 un-built dependencies. If |
| /// `None` is returned then no packages are ready to be built. |
| pub fn dequeue(&mut self) -> Option<(N, V, usize)> { |
| let (key, priority) = self |
| .dep_map |
| .iter() |
| .filter(|(_, (deps, _))| deps.is_empty()) |
| .map(|(key, _)| (key.clone(), self.priority[key])) |
| .max_by_key(|(_, priority)| *priority)?; |
| let (_, data) = self.dep_map.remove(&key).unwrap(); |
| Some((key, data, priority)) |
| } |
| |
| /// Returns `true` if there are remaining packages to be built. |
| pub fn is_empty(&self) -> bool { |
| self.dep_map.is_empty() |
| } |
| |
| /// Returns the number of remaining packages to be built. |
| pub fn len(&self) -> usize { |
| self.dep_map.len() |
| } |
| |
| /// Indicate that something has finished. |
| /// |
| /// Calling this function indicates that the `node` has produced `edge`. All |
| /// remaining work items which only depend on this node/edge pair are now |
| /// candidates to start their job. |
| /// |
| /// Returns the nodes that are now allowed to be dequeued as a result of |
| /// finishing this node. |
| pub fn finish(&mut self, node: &N, edge: &E) -> Vec<&N> { |
| // hashset<Node> |
| let reverse_deps = self.reverse_dep_map.get(node).and_then(|map| map.get(edge)); |
| let Some(reverse_deps) = reverse_deps else { |
| return Vec::new(); |
| }; |
| let key = (node.clone(), edge.clone()); |
| let mut result = Vec::new(); |
| for dep in reverse_deps.iter() { |
| let edges = &mut self.dep_map.get_mut(dep).unwrap().0; |
| assert!(edges.remove(&key)); |
| if edges.is_empty() { |
| result.push(dep); |
| } |
| } |
| result |
| } |
| } |
| |
| #[cfg(test)] |
| mod test { |
| use super::DependencyQueue; |
| |
| #[test] |
| fn deep_first_equal_cost() { |
| let mut q = DependencyQueue::new(); |
| |
| q.queue(1, (), vec![], 1); |
| q.queue(2, (), vec![(1, ())], 1); |
| q.queue(3, (), vec![], 1); |
| q.queue(4, (), vec![(2, ()), (3, ())], 1); |
| q.queue(5, (), vec![(4, ()), (3, ())], 1); |
| q.queue_finished(); |
| |
| assert_eq!(q.dequeue(), Some((1, (), 5))); |
| assert_eq!(q.dequeue(), Some((3, (), 4))); |
| assert_eq!(q.dequeue(), None); |
| q.finish(&3, &()); |
| assert_eq!(q.dequeue(), None); |
| q.finish(&1, &()); |
| assert_eq!(q.dequeue(), Some((2, (), 4))); |
| assert_eq!(q.dequeue(), None); |
| q.finish(&2, &()); |
| assert_eq!(q.dequeue(), Some((4, (), 3))); |
| assert_eq!(q.dequeue(), None); |
| q.finish(&4, &()); |
| assert_eq!(q.dequeue(), Some((5, (), 2))); |
| } |
| |
| #[test] |
| fn sort_by_highest_cost() { |
| let mut q = DependencyQueue::new(); |
| |
| q.queue(1, (), vec![], 1); |
| q.queue(2, (), vec![(1, ())], 1); |
| q.queue(3, (), vec![], 4); |
| q.queue(4, (), vec![(2, ()), (3, ())], 1); |
| q.queue_finished(); |
| |
| assert_eq!(q.dequeue(), Some((3, (), 9))); |
| assert_eq!(q.dequeue(), Some((1, (), 4))); |
| assert_eq!(q.dequeue(), None); |
| q.finish(&3, &()); |
| assert_eq!(q.dequeue(), None); |
| q.finish(&1, &()); |
| assert_eq!(q.dequeue(), Some((2, (), 3))); |
| assert_eq!(q.dequeue(), None); |
| q.finish(&2, &()); |
| assert_eq!(q.dequeue(), Some((4, (), 2))); |
| assert_eq!(q.dequeue(), None); |
| q.finish(&4, &()); |
| assert_eq!(q.dequeue(), None); |
| } |
| } |