Mod sorting

This commit is contained in:
Christian Weeks committed 2012-04-03 12:03:21 -04:00
1 parent ec4750f422
commit c4fd53d628
9 files changed
+535 -52

No files matched your search

@@ -0,0 +1,2 @@
eclipse.preferences.version=1
org.eclipse.ltk.core.refactoring.enable.project.refactoring.history=false
+4
View File
@@ -0,0 +1,4 @@
activeProfiles=
eclipse.preferences.version=1
resolveWorkspaceProjects=true
version=1
@@ -18,8 +18,16 @@ import java.util.List;
public class FMLModContainer implements ModContainer {
private Mod modDescriptor;
private Object modInstance;
private String source;
public FMLModContainer(String source) {
this.source=source;
}
public FMLModContainer(Class<?> clazz) {
if (clazz==null) {
return;
}
modDescriptor = clazz.getAnnotation(Mod.class);
try {
@@ -86,8 +94,7 @@ public class FMLModContainer implements ModContainer {
@Override
public String getSource() {
// TODO Auto-generated method stub
return null;
return source;
}
@Override
@@ -189,4 +196,11 @@ public class FMLModContainer implements ModContainer {
return new ArrayList<String>(0);
}
/* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return getSource();
}
}
+13
View File
@@ -31,6 +31,7 @@ import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import cpw.mods.fml.common.toposort.ModSorter;
import cpw.mods.fml.server.FMLHandler;
public class Loader {
@@ -78,6 +79,7 @@ public class Loader {
}
private void sortModList() {
log.fine("Verifying mod dependencies are satisfied");
for (ModContainer mod : mods) {
if (!namedMods.keySet().containsAll(mod.getDependencies())) {
log.severe(String.format("The mod %s requires mods %s to be available, one or more are not", mod.getName(),mod.getDependencies()));
@@ -86,6 +88,17 @@ public class Loader {
throw new LoaderException();
}
}
log.fine("All dependencies are satisfied");
ModSorter sorter=new ModSorter(mods,namedMods);
try {
log.fine("Sorting mods into an ordered list");
mods=sorter.sort();
log.fine(String.format("Mod list sorted %s",mods));
} catch (IllegalArgumentException iae) {
log.severe("A dependency cycle was detected in the input mod set so they cannot load them in order");
log.throwing("Loader", "sortModList", iae);
throw new LoaderException(iae);
}
}
private void preModInit() {
@@ -0,0 +1,93 @@
/*
* The FML Forge Mod Loader suite. Copyright (C) 2012 cpw
*
* This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 2.1 of the License, or any later version.
*
* This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package cpw.mods.fml.common.toposort;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import cpw.mods.fml.common.FMLModContainer;
import cpw.mods.fml.common.ModContainer;
import cpw.mods.fml.common.toposort.TopologicalSort.DirectedGraph;
/**
* @author cpw
*
*/
public class ModSorter {
private DirectedGraph<ModContainer> modGraph;
private ModContainer beforeAll = new FMLModContainer("DummyBeforeAll");
private ModContainer afterAll = new FMLModContainer("DummyAfterAll");
private ModContainer before = new FMLModContainer("DummyBefore");
private ModContainer after = new FMLModContainer("DummyAfter");
public ModSorter(List<ModContainer> modList, Map<String, ModContainer> nameLookup) {
buildGraph(modList, nameLookup);
}
private void buildGraph(List<ModContainer> modList, Map<String, ModContainer> nameLookup) {
modGraph = new DirectedGraph<ModContainer>();
modGraph.addNode(beforeAll);
modGraph.addNode(before);
modGraph.addNode(afterAll);
modGraph.addNode(after);
modGraph.addEdge(before, after);
modGraph.addEdge(beforeAll, before);
modGraph.addEdge(after, afterAll);
for (ModContainer mod : modList) {
modGraph.addNode(mod);
}
for (ModContainer mod : modList) {
boolean preDepAdded=false;
boolean postDepAdded=false;
for (String dep : mod.getPreDepends()) {
preDepAdded=true;
if (dep.equals("*")) {
// We are "after" everything
modGraph.addEdge(mod, afterAll);
modGraph.addEdge(after, mod);
postDepAdded=true;
} else {
modGraph.addEdge(before, mod);
modGraph.addEdge(nameLookup.get(dep), mod);
}
}
for (String dep : mod.getPostDepends()) {
postDepAdded=true;
if (dep.equals("*")) {
// We are "before" everything
modGraph.addEdge(beforeAll, mod);
modGraph.addEdge(mod, before);
preDepAdded=true;
} else {
modGraph.addEdge(mod, after);
modGraph.addEdge(mod, nameLookup.get(dep));
}
}
if (!preDepAdded) {
modGraph.addEdge(before, mod);
}
if (!postDepAdded) {
modGraph.addEdge(mod, after);
}
}
}
public List<ModContainer> sort() {
List<ModContainer> sortedList=TopologicalSort.topologicalSort(modGraph);
sortedList.removeAll(Arrays.asList(new ModContainer[] {beforeAll,before,after,afterAll}));
return sortedList;
}
}
@@ -0,0 +1,155 @@
/*
* The FML Forge Mod Loader suite.
* Copyright (C) 2012 cpw
*
* This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 2.1 of the License, or any later version.
*
* This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package cpw.mods.fml.common.toposort;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
/**
* Topological sort for mod loading
*
* Based on a variety of sources, including http://keithschwarz.com/interesting/code/?dir=topological-sort
* @author cpw
*
*/
public class TopologicalSort {
public static class DirectedGraph<T> implements Iterable<T> {
private final Map<T, Set<T>> graph=new HashMap<T,Set<T>>();
public boolean addNode(T node) {
// Ignore nodes already added
if (graph.containsKey(node)) {
return false;
}
graph.put(node, new HashSet<T>());
return true;
}
public void addEdge(T from, T to) {
if (!(graph.containsKey(from) && graph.containsKey(to))) {
throw new NoSuchElementException("Missing nodes from graph");
}
graph.get(from).add(to);
}
public void removeEdge(T from, T to) {
if (!(graph.containsKey(from) && graph.containsKey(to))) {
throw new NoSuchElementException("Missing nodes from graph");
}
graph.get(from).remove(to);
}
public boolean edgeExists(T from, T to) {
if (!(graph.containsKey(from) && graph.containsKey(to))) {
throw new NoSuchElementException("Missing nodes from graph");
}
return graph.get(from).contains(to);
}
public Set<T> edgesFrom(T from) {
if (!graph.containsKey(from)) {
throw new NoSuchElementException("Missing node from graph");
}
return Collections.unmodifiableSet(graph.get(from));
}
@Override
public Iterator<T> iterator() {
return graph.keySet().iterator();
}
public int size() {
return graph.size();
}
public boolean isEmpty() {
return graph.isEmpty();
}
@Override
public String toString() {
return graph.toString();
}
}
/**
* Sort the input graph into a topologically sorted list
*
* Uses the reverse depth first search as outlined in ...
* @param graph
* @return
*/
public static <T> List<T> topologicalSort(DirectedGraph<T> graph) {
DirectedGraph<T> rGraph=reverse(graph);
List<T> sortedResult=new ArrayList<T>();
Set<T> visitedNodes=new HashSet<T>();
// A list of "fully explored" nodes. Leftovers in here indicate cycles in the graph
Set<T> expandedNodes=new HashSet<T>();
for (T node : rGraph) {
explore(node, rGraph, sortedResult, visitedNodes, expandedNodes);
}
return sortedResult;
}
public static <T> DirectedGraph<T> reverse(DirectedGraph<T> graph) {
DirectedGraph<T> result=new DirectedGraph<T>();
for (T node : graph) {
result.addNode(node);
}
for (T from : graph) {
for (T to : graph.edgesFrom(from)) {
result.addEdge(to, from);
}
}
return result;
}
public static <T> void explore(T node, DirectedGraph<T> graph, List<T> sortedResult, Set<T> visitedNodes, Set<T> expandedNodes) {
// Have we been here before?
if (visitedNodes.contains(node)) {
// And have completed this node before
if (expandedNodes.contains(node)) {
// Then we're fine
return;
}
System.out.printf("%s: %s\n%s\n%s\n",node,sortedResult,visitedNodes,expandedNodes);
throw new IllegalArgumentException("There was a cycle detected in the input graph, sorting is not possible");
}
// Visit this node
visitedNodes.add(node);
// Recursively explore inbound edges
for (T inbound : graph.edgesFrom(node)) {
explore(inbound, graph, sortedResult, visitedNodes, expandedNodes);
}
// Add ourselves now
sortedResult.add(node);
// And mark ourselves as explored
expandedNodes.add(node);
}
}
@@ -14,7 +14,6 @@
package cpw.mods.fml.server;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.StringTokenizer;
@@ -32,6 +31,9 @@ public class ModLoaderModContainer implements ModContainer {
private BaseMod mod;
private boolean isTicking;
private String modSource ;
private ArrayList<String> dependencies;
private ArrayList<String> preDependencies;
private ArrayList<String> postDependencies;
public ModLoaderModContainer(Class<? extends BaseMod> modClazz, String modSource) {
this.modClazz=modClazz;
this.modSource =modSource;
@@ -174,15 +176,13 @@ public class ModLoaderModContainer implements ModContainer {
public ICraftingHandler getCraftingHandler() {
return mod;
}
/* (non-Javadoc)
* @see cpw.mods.fml.common.ModContainer#getDependencies()
*/
@Override
public List<String> getDependencies() {
ArrayList<String> dependencies=new ArrayList<String>(10);
private void computeDependencies() {
dependencies = new ArrayList<String>();
preDependencies = new ArrayList<String>();
postDependencies = new ArrayList<String>();
if (mod.getPriorities()==null || mod.getPriorities().length()==0) {
return dependencies;
return;
}
StringTokenizer st=new StringTokenizer(mod.getPriorities(),";");
for (; st.hasMoreTokens(); ) {
@@ -194,19 +194,44 @@ public class ModLoaderModContainer implements ModContainer {
if ("required-before".equals(depparts[0]) || "required-after".equals(depparts[0])) {
dependencies.add(depparts[1]);
}
if ("required-before".equals(depparts[0]) || "before".equals(depparts[0])) {
preDependencies.add(depparts[1]);
}
if ("required-after".equals(depparts[0]) || "after".equals(depparts[0])) {
postDependencies.add(depparts[1]);
}
}
}
/* (non-Javadoc)
* @see cpw.mods.fml.common.ModContainer#getDependencies()
*/
@Override
public List<String> getDependencies() {
if (dependencies==null) {
computeDependencies();
}
return dependencies;
}
@Override
public List<String> getPreDepends() {
// TODO Auto-generated method stub
return null;
if (dependencies==null) {
computeDependencies();
}
return preDependencies;
}
@Override
public List<String> getPostDepends() {
// TODO Auto-generated method stub
return null;
if (dependencies==null) {
computeDependencies();
}
return postDependencies;
}
public String toString() {
return modSource;
}
}
+214
View File
@@ -0,0 +1,214 @@
/*
* The FML Forge Mod Loader suite.
* Copyright (C) 2012 cpw
*
* This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 2.1 of the License, or any later version.
*
* This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package cpw.mods.fml.test;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import net.minecraft.src.BaseMod;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.*;
import cpw.mods.fml.common.ModContainer;
import cpw.mods.fml.common.toposort.ModSorter;
import cpw.mods.fml.server.ModLoaderModContainer;
public class LoaderTests {
private ModContainer mc1;
private ModContainer mc2;
private ModContainer mc3;
private ModContainer mc4;
private ModContainer mc5;
private ModContainer mc6;
private ModContainer mc7;
private ModContainer mc8;
@Before
public void setUp() throws Exception {
mc1 = new ModLoaderModContainer(Mod1.class, "Test1");
mc1.preInit();
mc2 = new ModLoaderModContainer(Mod2.class, "Test2");
mc2.preInit();
mc3 = new ModLoaderModContainer(Mod3.class, "Test3");
mc3.preInit();
mc4 = new ModLoaderModContainer(Mod4.class, "Test4");
mc4.preInit();
mc5 = new ModLoaderModContainer(Mod5.class, "Test5");
mc5.preInit();
mc6 = new ModLoaderModContainer(Mod5.class, "Test6");
mc6.preInit();
mc7 = new ModLoaderModContainer(Mod5.class, "Test7");
mc7.preInit();
mc8 = new ModLoaderModContainer(Mod5.class, "Test8");
mc8.preInit();
}
@After
public void tearDown() throws Exception {
}
@Test
public void testModSorting() {
HashMap<String,ModContainer> modList=new HashMap<String,ModContainer>();
modList.put("Mod1",mc1);
modList.put("Mod2",mc2);
modList.put("Mod3",mc3);
modList.put("Mod4",mc4);
modList.put("Mod5",mc5);
modList.put("Mod6",mc6);
modList.put("Mod7",mc7);
modList.put("Mod8",mc8);
ModSorter ms=new ModSorter(Collections.list(Collections.enumeration(modList.values())),modList);
List<ModContainer> mods=ms.sort();
assertEquals("Eight mods",8,mods.size());
System.out.printf("%s\n",mods);
}
@Test
public void testModPrioritiesParsing() {
// Mod 1
assertTrue("Empty hard dependencies for Mod1", mc1.getDependencies().isEmpty());
assertTrue("Mod1 predepends on *",mc1.getPreDepends().contains("*"));
// Mod 2
assertTrue("Empty hard dependencies for Mod2", mc2.getDependencies().isEmpty());
assertTrue("Mod2 postdepends on *",mc1.getPostDepends().contains("*"));
// Mod 3
assertEquals("Two hard dependencies for Mod3", 2, mc1.getDependencies().size());
assertTrue("Hard dependencies for Mod3 contains Mod4", mc1.getDependencies().contains("Mod4"));
assertTrue("Hard dependencies for Mod3 contains Mod2", mc1.getDependencies().contains("Mod2"));
assertEquals("One pre depends for Mod3", 1, mc1.getPreDepends().size());
assertEquals("One post depends for Mod3", 1, mc1.getPostDepends().size());
// Mod 4
assertTrue("Empty hard dependencies for Mod1", mc1.getDependencies().isEmpty());
assertTrue("Mod1 predepends on *",mc1.getPreDepends().contains("*"));
// Mod 5
assertTrue("Empty hard dependencies for Mod1", mc1.getDependencies().isEmpty());
assertTrue("Mod1 predepends on *",mc1.getPreDepends().contains("*"));
}
public static class Mod1 extends BaseMod {
@Override
public String getVersion() {
return null;
}
@Override
public void load() {
}
@Override
public String getPriorities() {
return "before:*";
}
}
public static class Mod2 extends BaseMod {
@Override
public String getVersion() {
return null;
}
@Override
public void load() {
}
@Override
public String getPriorities() {
return "after:*";
}
}
public static class Mod3 extends BaseMod {
@Override
public String getVersion() {
return null;
}
@Override
public void load() {
}
@Override
public String getPriorities() {
return "required-before:Mod4;required-after:Mod5";
}
}
public static class Mod4 extends BaseMod {
@Override
public String getVersion() {
return null;
}
@Override
public void load() {
}
@Override
public String getPriorities() {
return "required-before:Mod2;after:Mod3;required-after:Mod1";
}
}
public static class Mod5 extends BaseMod {
@Override
public String getVersion() {
return null;
}
@Override
public void load() {
}
@Override
public String getPriorities() {
return "";
}
}
public static class Mod6 extends BaseMod {
@Override
public String getVersion() {
return null;
}
@Override
public void load() {
}
@Override
public String getPriorities() {
return "";
}
}
public static class Mod7 extends BaseMod {
@Override
public String getVersion() {
return null;
}
@Override
public void load() {
}
@Override
public String getPriorities() {
return "";
}
}
public static class Mod8 extends BaseMod {
@Override
public String getVersion() {
return null;
}
@Override
public void load() {
}
@Override
public String getPriorities() {
return "before:Mod7";
}
}
}
-37
View File
@@ -1,37 +0,0 @@
/*
* The FML Forge Mod Loader suite.
* Copyright (C) 2012 cpw
*
* This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 2.1 of the License, or any later version.
*
* This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package fml.test;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import cpw.mods.fml.common.Loader;
public class LoaderTests {
@Before
public void setUp() throws Exception {
}
@After
public void tearDown() throws Exception {
}
@Test
public void testModLoading() {
Loader.instance().loadMods();
}
}