diff --git a/.settings/org.eclipse.ltk.core.refactoring.prefs b/.settings/org.eclipse.ltk.core.refactoring.prefs new file mode 100644 index 0000000000..b196c64a34 --- /dev/null +++ b/.settings/org.eclipse.ltk.core.refactoring.prefs @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +org.eclipse.ltk.core.refactoring.enable.project.refactoring.history=false diff --git a/.settings/org.eclipse.m2e.core.prefs b/.settings/org.eclipse.m2e.core.prefs new file mode 100644 index 0000000000..f897a7f1cb --- /dev/null +++ b/.settings/org.eclipse.m2e.core.prefs @@ -0,0 +1,4 @@ +activeProfiles= +eclipse.preferences.version=1 +resolveWorkspaceProjects=true +version=1 diff --git a/common/cpw/mods/fml/common/FMLModContainer.java b/common/cpw/mods/fml/common/FMLModContainer.java index 77184e7a48..c33b911961 100644 --- a/common/cpw/mods/fml/common/FMLModContainer.java +++ b/common/cpw/mods/fml/common/FMLModContainer.java @@ -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(0); } + /* (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return getSource(); + } } diff --git a/common/cpw/mods/fml/common/Loader.java b/common/cpw/mods/fml/common/Loader.java index 8ca6b7cbe7..9cbad6010a 100644 --- a/common/cpw/mods/fml/common/Loader.java +++ b/common/cpw/mods/fml/common/Loader.java @@ -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() { diff --git a/common/cpw/mods/fml/common/toposort/ModSorter.java b/common/cpw/mods/fml/common/toposort/ModSorter.java new file mode 100644 index 0000000000..5115fb49e3 --- /dev/null +++ b/common/cpw/mods/fml/common/toposort/ModSorter.java @@ -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 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 modList, Map nameLookup) { + buildGraph(modList, nameLookup); + } + + private void buildGraph(List modList, Map nameLookup) { + modGraph = new DirectedGraph(); + 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 sort() { + List sortedList=TopologicalSort.topologicalSort(modGraph); + sortedList.removeAll(Arrays.asList(new ModContainer[] {beforeAll,before,after,afterAll})); + return sortedList; + } +} diff --git a/common/cpw/mods/fml/common/toposort/TopologicalSort.java b/common/cpw/mods/fml/common/toposort/TopologicalSort.java new file mode 100644 index 0000000000..37b5bf2afb --- /dev/null +++ b/common/cpw/mods/fml/common/toposort/TopologicalSort.java @@ -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 implements Iterable { + private final Map> graph=new HashMap>(); + + public boolean addNode(T node) { + // Ignore nodes already added + if (graph.containsKey(node)) { + return false; + } + graph.put(node, new HashSet()); + 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 edgesFrom(T from) { + if (!graph.containsKey(from)) { + throw new NoSuchElementException("Missing node from graph"); + } + return Collections.unmodifiableSet(graph.get(from)); + } + @Override + public Iterator 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 List topologicalSort(DirectedGraph graph) { + DirectedGraph rGraph=reverse(graph); + + List sortedResult=new ArrayList(); + Set visitedNodes=new HashSet(); + + // A list of "fully explored" nodes. Leftovers in here indicate cycles in the graph + Set expandedNodes=new HashSet(); + + for (T node : rGraph) { + explore(node, rGraph, sortedResult, visitedNodes, expandedNodes); + } + + return sortedResult; + } + + public static DirectedGraph reverse(DirectedGraph graph) { + DirectedGraph result=new DirectedGraph(); + 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 void explore(T node, DirectedGraph graph, List sortedResult, Set visitedNodes, Set 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); + } +} diff --git a/server/cpw/mods/fml/server/ModLoaderModContainer.java b/server/cpw/mods/fml/server/ModLoaderModContainer.java index 8c57058564..922e4f8d4f 100644 --- a/server/cpw/mods/fml/server/ModLoaderModContainer.java +++ b/server/cpw/mods/fml/server/ModLoaderModContainer.java @@ -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 dependencies; + private ArrayList preDependencies; + private ArrayList postDependencies; public ModLoaderModContainer(Class 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 getDependencies() { - ArrayList dependencies=new ArrayList(10); + + private void computeDependencies() { + dependencies = new ArrayList(); + preDependencies = new ArrayList(); + postDependencies = new ArrayList(); 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 getDependencies() { + if (dependencies==null) { + computeDependencies(); } return dependencies; } @Override public List getPreDepends() { - // TODO Auto-generated method stub - return null; + if (dependencies==null) { + computeDependencies(); + } + return preDependencies; } @Override public List getPostDepends() { - // TODO Auto-generated method stub - return null; + if (dependencies==null) { + computeDependencies(); + } + return postDependencies; + } + + public String toString() { + return modSource; } } diff --git a/test/cpw/mods/fml/test/LoaderTests.java b/test/cpw/mods/fml/test/LoaderTests.java new file mode 100644 index 0000000000..28e1b41864 --- /dev/null +++ b/test/cpw/mods/fml/test/LoaderTests.java @@ -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 modList=new HashMap(); + 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 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"; + } + } +} diff --git a/test/fml/test/LoaderTests.java b/test/fml/test/LoaderTests.java deleted file mode 100644 index 8330e88c9d..0000000000 --- a/test/fml/test/LoaderTests.java +++ /dev/null @@ -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(); - } -}