Skip to content

Commit

Permalink
ReverseTopological -> AdjacencyMap (vercel/turborepo#5430)
Browse files Browse the repository at this point in the history
### Description

This PR renames the `ReverseTopological` graph store implementation to
`AdjacencyMap`. The reasoning here is that we can implement any graph
traversal algorithm on top of `AdjacencyMap`, not just reverse
topological.

This PR also adds a `reverse_topological_from_node` iterator method to
only traverse the subgraph from a given node.

Next.js PR:
https://github.com/vercel/next.js/compare/alexkirsz/web-866-adjacency-map?expand=1

### Testing Instructions

N/A
  • Loading branch information
alexkirsz authored Jul 3, 2023
1 parent 900ba16 commit 1e53966
Show file tree
Hide file tree
Showing 8 changed files with 222 additions and 145 deletions.
203 changes: 203 additions & 0 deletions crates/turbo-tasks/src/graph/adjacency_map.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
use std::collections::{HashMap, HashSet};

use super::graph_store::{GraphNode, GraphStore};

/// A graph traversal that builds an adjacency map
pub struct AdjacencyMap<T>
where
T: Eq + std::hash::Hash + Clone,
{
adjacency_map: HashMap<T, Vec<T>>,
roots: Vec<T>,
}

impl<T> Default for AdjacencyMap<T>
where
T: Eq + std::hash::Hash + Clone,
{
fn default() -> Self {
Self::new()
}
}

impl<T> AdjacencyMap<T>
where
T: Eq + std::hash::Hash + Clone,
{
/// Creates a new adjacency map
pub fn new() -> Self {
Self {
adjacency_map: HashMap::new(),
roots: Vec::new(),
}
}

/// Returns an iterator over the root nodes of the graph
pub fn roots(&self) -> impl Iterator<Item = &T> {
self.roots.iter()
}

/// Returns an iterator over the children of the given node
pub fn get(&self, node: &T) -> Option<impl Iterator<Item = &T>> {
self.adjacency_map.get(node).map(|vec| vec.iter())
}
}

impl<T> GraphStore for AdjacencyMap<T>
where
T: Eq + std::hash::Hash + Clone,
{
type Node = T;
type Handle = T;

fn insert(&mut self, from_handle: Option<T>, node: GraphNode<T>) -> Option<(Self::Handle, &T)> {
let vec = if let Some(from_handle) = from_handle {
self.adjacency_map
.entry(from_handle)
.or_insert_with(|| Vec::with_capacity(1))
} else {
&mut self.roots
};

vec.push(node.node().clone());
Some((node.into_node(), vec.last().unwrap()))
}
}

impl<T> AdjacencyMap<T>
where
T: Eq + std::hash::Hash + Clone,
{
/// Returns an iterator over the nodes in reverse topological order,
/// starting from the roots.
pub fn into_reverse_topological(self) -> ReverseTopologicalIter<T> {
ReverseTopologicalIter {
adjacency_map: self.adjacency_map,
stack: self
.roots
.into_iter()
.map(|root| (ReverseTopologicalPass::Pre, root))
.collect(),
visited: HashSet::new(),
}
}

/// Returns an iterator over the nodes in reverse topological order,
/// starting from the given node.
pub fn reverse_topological_from_node<'graph>(
&'graph self,
node: &'graph T,
) -> ReverseTopologicalFromNodeIter<'graph, T> {
ReverseTopologicalFromNodeIter {
adjacency_map: &self.adjacency_map,
stack: vec![(ReverseTopologicalPass::Pre, node)],
visited: HashSet::new(),
}
}
}

#[derive(Debug)]
enum ReverseTopologicalPass {
Pre,
Post,
}

/// An iterator over the nodes of a graph in reverse topological order, starting
/// from the roots.
pub struct ReverseTopologicalIter<T>
where
T: Eq + std::hash::Hash + Clone,
{
adjacency_map: HashMap<T, Vec<T>>,
stack: Vec<(ReverseTopologicalPass, T)>,
visited: HashSet<T>,
}

impl<T> Iterator for ReverseTopologicalIter<T>
where
T: Eq + std::hash::Hash + Clone,
{
type Item = T;

fn next(&mut self) -> Option<Self::Item> {
let current = loop {
let (pass, current) = self.stack.pop()?;

match pass {
ReverseTopologicalPass::Post => {
break current;
}
ReverseTopologicalPass::Pre => {
if self.visited.contains(&current) {
continue;
}

self.visited.insert(current.clone());

let Some(neighbors) = self.adjacency_map.get(&current) else {
break current;
};

self.stack.push((ReverseTopologicalPass::Post, current));
self.stack.extend(
neighbors
.iter()
.map(|neighbor| (ReverseTopologicalPass::Pre, neighbor.clone())),
);
}
}
};

Some(current)
}
}

/// An iterator over the nodes of a graph in reverse topological order, starting
/// from a given node.
pub struct ReverseTopologicalFromNodeIter<'graph, T>
where
T: Eq + std::hash::Hash + Clone,
{
adjacency_map: &'graph HashMap<T, Vec<T>>,
stack: Vec<(ReverseTopologicalPass, &'graph T)>,
visited: HashSet<&'graph T>,
}

impl<'graph, T> Iterator for ReverseTopologicalFromNodeIter<'graph, T>
where
T: Eq + std::hash::Hash + Clone,
{
type Item = &'graph T;

fn next(&mut self) -> Option<Self::Item> {
let current = loop {
let (pass, current) = self.stack.pop()?;

match pass {
ReverseTopologicalPass::Post => {
break current;
}
ReverseTopologicalPass::Pre => {
if self.visited.contains(&current) {
continue;
}

self.visited.insert(current);

let Some(neighbors) = self.adjacency_map.get(current) else {
break current;
};

self.stack.push((ReverseTopologicalPass::Post, current));
self.stack.extend(
neighbors
.iter()
.map(|neighbor| (ReverseTopologicalPass::Pre, neighbor)),
);
}
}
};

Some(current)
}
}
4 changes: 2 additions & 2 deletions crates/turbo-tasks/src/graph/mod.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
mod adjacency_map;
mod control_flow;
mod graph_store;
mod graph_traversal;
mod non_deterministic;
mod reverse_topological;
mod visit;
mod with_future;

pub use adjacency_map::AdjacencyMap;
pub use control_flow::VisitControlFlow;
pub use graph_store::{GraphStore, SkipDuplicates};
pub use graph_traversal::{GraphTraversal, GraphTraversalResult};
pub use non_deterministic::NonDeterministic;
pub use reverse_topological::ReverseTopological;
pub use visit::Visit;
128 changes: 0 additions & 128 deletions crates/turbo-tasks/src/graph/reverse_topological.rs

This file was deleted.

6 changes: 3 additions & 3 deletions crates/turbopack-build/src/chunking_context.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use anyhow::Result;
use indexmap::IndexSet;
use turbo_tasks::{
graph::{GraphTraversal, ReverseTopological},
graph::{AdjacencyMap, GraphTraversal},
primitives::{BoolVc, StringVc},
TryJoinIterExt, Value,
};
Expand Down Expand Up @@ -322,7 +322,7 @@ async fn get_parallel_chunks<I>(entries: I) -> Result<impl Iterator<Item = Chunk
where
I: IntoIterator<Item = ChunkVc>,
{
Ok(ReverseTopological::new()
Ok(AdjacencyMap::new()
.skip_duplicates()
.visit(entries, |chunk: ChunkVc| async move {
Ok(chunk
Expand All @@ -336,7 +336,7 @@ where
.await
.completed()?
.into_inner()
.into_iter())
.into_reverse_topological())
}

async fn get_optimized_chunks<I>(chunks: I) -> Result<ChunksVc>
Expand Down
Loading

0 comments on commit 1e53966

Please sign in to comment.