blob: 1f5aebe92b50709dd79d62d7cd7f0226bed6fdbf [file] [edit]
//! 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);
}
}