diff --git a/src/java/com/threerings/presents/server/ShutdownManager.java b/src/java/com/threerings/presents/server/ShutdownManager.java index f948ba2b7..fe296217e 100644 --- a/src/java/com/threerings/presents/server/ShutdownManager.java +++ b/src/java/com/threerings/presents/server/ShutdownManager.java @@ -21,8 +21,6 @@ package com.threerings.presents.server; -import java.util.ArrayList; -import java.util.HashMap; import com.google.inject.Inject; import com.google.inject.Singleton; @@ -31,6 +29,7 @@ import com.samskivert.util.ObserverList; import com.samskivert.util.RunQueue; import com.threerings.presents.annotation.EventQueue; +import com.threerings.util.DependencyGraph; import static com.threerings.presents.Log.log; @@ -126,149 +125,6 @@ public class ShutdownManager }); } - /** - * We maintain a bidirectional graph to manage the order that the items are removed. Children - * must wait until their parents are accessed - thus removing an available element means that - * a node without parents (an orphan) is removed and returned. - * @param - */ - protected class DependencyGraph - { - /** - * Adds an element with no initial dependencies from the graph. - */ - public void add (T element) - { - DependencyNode node = new DependencyNode(element); - _nodes.put(element, node); - _orphans.add(element); - } - - /** - * Removes an element and its dependencies from the graph. - */ - public void remove (T element) - { - DependencyNode node = _nodes.remove(element); - _orphans.remove(element); - - // Remove ourselves as a child of our parents. - for (DependencyNode parent : node.parents) { - parent.children.remove(node); - } - - // Remove ourselves as a parent of our children, possibly orphaning them. - for (DependencyNode child : node.children) { - child.parents.remove(node); - if (child.parents.isEmpty()) { - _orphans.add(child.content); - } - } - } - - /** - * Removes and returns an element which is available, meaning not dependent upon any other - * still in the graph. - */ - public T removeAvailableElement () - { - T elem = _orphans.get(0); - DependencyNode node = _nodes.get(elem); - remove(elem); - - return elem; - } - - /** - * Returns the number of elements in the graph. - */ - public int size () - { - return _nodes.size(); - } - - /** - * Returns whether there are no more elements in the graph. - */ - public boolean isEmpty () - { - return size() == 0; - } - - /** - * Records a new dependency of the dependant upon the dependee. - */ - public void addDependency (T dependant, T dependee) - throws IllegalArgumentException - { - _orphans.remove(dependant); - DependencyNode dependantNode = _nodes.get(dependant); - DependencyNode dependeeNode = _nodes.get(dependee); - - if (dependsOn(dependee, dependant)) { - throw new IllegalArgumentException("Refusing to create circular dependency."); - } - dependantNode.parents.add(dependeeNode); - dependeeNode.children.add(dependantNode); - - } - - /** - * Returns whether elem1 is designated to depend on elem2. - */ - public boolean dependsOn (T elem1, T elem2) - { - DependencyNode node1 = _nodes.get(elem1); - DependencyNode node2 = _nodes.get(elem2); - - ArrayList> nodesToCheck = new ArrayList>(); - ArrayList> nodesAlreadyChecked = new ArrayList>(); - nodesToCheck.addAll(node1.parents); - - // We prevent circular dependencies when we add dependencies. Otherwise, this'd be - // potentially non-terminating. - while (!nodesToCheck.isEmpty()) { - // We take it off the end since we don't care about order and this is faster. - DependencyNode checkNode = nodesToCheck.remove(nodesToCheck.size() - 1); - - if (nodesAlreadyChecked.contains(checkNode)) { - // We've seen him before, no need to check again. - continue; - - } else if (checkNode == node2) { - // We've found our dependency - return true; - - } else { - nodesAlreadyChecked.add(checkNode); - nodesToCheck.addAll(checkNode.parents); - } - } - - return false; - } - - /** All the nodes included in the graph. */ - protected HashMap> _nodes = new HashMap>(); - - /** Nodes in the graph with no parents/dependencies. */ - protected ArrayList _orphans = new ArrayList(); - - protected class DependencyNode - { - public T content; - public ArrayList> parents; - public ArrayList> children; - - public DependencyNode (T contents) - { - this.content = contents; - this.parents = new ArrayList>(); - this.children = new ArrayList>(); - } - } - } - /** All of the registered shutdowners along with related constraints. */ protected DependencyGraph _downers = new DependencyGraph(); diff --git a/src/java/com/threerings/util/DependencyGraph.java b/src/java/com/threerings/util/DependencyGraph.java new file mode 100644 index 000000000..d1b039b22 --- /dev/null +++ b/src/java/com/threerings/util/DependencyGraph.java @@ -0,0 +1,148 @@ +package com.threerings.util; + +import java.util.ArrayList; +import java.util.HashMap; + +/** + * Maintains a bidirectional graph to manage the order that the items are removed. Children + * must wait until their parents are accessed - thus removing an available element means that + * a node without parents (an orphan) is removed and returned and the rest of the graph is + * updated to reflect that removal. + * @param + */ +public class DependencyGraph +{ + /** + * Adds an element with no initial dependencies from the graph. + */ + public void add (T element) + { + DependencyNode node = new DependencyNode(element); + _nodes.put(element, node); + _orphans.add(element); + } + + /** + * Removes an element and its dependencies from the graph. + */ + public void remove (T element) + { + DependencyNode node = _nodes.remove(element); + _orphans.remove(element); + + // Remove ourselves as a child of our parents. + for (DependencyNode parent : node.parents) { + parent.children.remove(node); + } + + // Remove ourselves as a parent of our children, possibly orphaning them. + for (DependencyNode child : node.children) { + child.parents.remove(node); + if (child.parents.isEmpty()) { + _orphans.add(child.content); + } + } + } + + /** + * Removes and returns an element which is available, meaning not dependent upon any other + * still in the graph. + */ + public T removeAvailableElement () + { + T elem = _orphans.get(0); + DependencyNode node = _nodes.get(elem); + remove(elem); + + return elem; + } + + /** + * Returns the number of elements in the graph. + */ + public int size () + { + return _nodes.size(); + } + + /** + * Returns whether there are no more elements in the graph. + */ + public boolean isEmpty () + { + return size() == 0; + } + + /** + * Records a new dependency of the dependant upon the dependee. + */ + public void addDependency (T dependant, T dependee) + throws IllegalArgumentException + { + _orphans.remove(dependant); + DependencyNode dependantNode = _nodes.get(dependant); + DependencyNode dependeeNode = _nodes.get(dependee); + + if (dependsOn(dependee, dependant)) { + throw new IllegalArgumentException("Refusing to create circular dependency."); + } + dependantNode.parents.add(dependeeNode); + dependeeNode.children.add(dependantNode); + + } + + /** + * Returns whether elem1 is designated to depend on elem2. + */ + public boolean dependsOn (T elem1, T elem2) + { + DependencyNode node1 = _nodes.get(elem1); + DependencyNode node2 = _nodes.get(elem2); + + ArrayList> nodesToCheck = new ArrayList>(); + ArrayList> nodesAlreadyChecked = new ArrayList>(); + nodesToCheck.addAll(node1.parents); + + // We prevent circular dependencies when we add dependencies. Otherwise, this'd be + // potentially non-terminating. + while (!nodesToCheck.isEmpty()) { + // We take it off the end since we don't care about order and this is faster. + DependencyNode checkNode = nodesToCheck.remove(nodesToCheck.size() - 1); + + if (nodesAlreadyChecked.contains(checkNode)) { + // We've seen him before, no need to check again. + continue; + + } else if (checkNode == node2) { + // We've found our dependency + return true; + + } else { + nodesAlreadyChecked.add(checkNode); + nodesToCheck.addAll(checkNode.parents); + } + } + + return false; + } + + /** All the nodes included in the graph. */ + protected HashMap> _nodes = new HashMap>(); + + /** Nodes in the graph with no parents/dependencies. */ + protected ArrayList _orphans = new ArrayList(); + + protected class DependencyNode + { + public T content; + public ArrayList> parents; + public ArrayList> children; + + public DependencyNode (T contents) + { + this.content = contents; + this.parents = new ArrayList>(); + this.children = new ArrayList>(); + } + } +} \ No newline at end of file