001 package cpw.mods.fml.common; 002 003 import java.lang.reflect.InvocationTargetException; 004 import java.util.List; 005 import java.util.Map; 006 import java.util.Map.Entry; 007 import java.util.logging.Level; 008 009 import com.google.common.base.Joiner; 010 import com.google.common.collect.ArrayListMultimap; 011 import com.google.common.collect.BiMap; 012 import com.google.common.collect.ImmutableBiMap; 013 import com.google.common.collect.ImmutableMap; 014 import com.google.common.collect.ImmutableMap.Builder; 015 import com.google.common.collect.Iterables; 016 import com.google.common.collect.Lists; 017 import com.google.common.collect.Multimap; 018 import com.google.common.eventbus.EventBus; 019 import com.google.common.eventbus.Subscribe; 020 021 import cpw.mods.fml.common.LoaderState.ModState; 022 import cpw.mods.fml.common.event.FMLLoadEvent; 023 import cpw.mods.fml.common.event.FMLPreInitializationEvent; 024 import cpw.mods.fml.common.event.FMLStateEvent; 025 026 public class LoadController 027 { 028 private Loader loader; 029 private EventBus masterChannel; 030 private ImmutableMap<String,EventBus> eventChannels; 031 private LoaderState state; 032 private Multimap<String, ModState> modStates = ArrayListMultimap.create(); 033 private Multimap<String, Throwable> errors = ArrayListMultimap.create(); 034 private Map<String, ModContainer> modList; 035 private List<ModContainer> activeModList = Lists.newArrayList(); 036 private ModContainer activeContainer; 037 private BiMap<ModContainer, Object> modObjectList; 038 039 public LoadController(Loader loader) 040 { 041 this.loader = loader; 042 this.masterChannel = new EventBus("FMLMainChannel"); 043 this.masterChannel.register(this); 044 045 state = LoaderState.NOINIT; 046 047 048 } 049 050 @Subscribe 051 public void buildModList(FMLLoadEvent event) 052 { 053 this.modList = loader.getIndexedModList(); 054 Builder<String, EventBus> eventBus = ImmutableMap.builder(); 055 056 for (ModContainer mod : loader.getModList()) 057 { 058 EventBus bus = new EventBus(mod.getModId()); 059 boolean isActive = mod.registerBus(bus, this); 060 if (isActive) 061 { 062 FMLLog.fine("Activating mod %s", mod.getModId()); 063 activeModList.add(mod); 064 modStates.put(mod.getModId(), ModState.UNLOADED); 065 eventBus.put(mod.getModId(), bus); 066 } 067 else 068 { 069 FMLLog.warning("Mod %s has been disabled through configuration", mod.getModId()); 070 modStates.put(mod.getModId(), ModState.UNLOADED); 071 modStates.put(mod.getModId(), ModState.DISABLED); 072 } 073 } 074 075 eventChannels = eventBus.build(); 076 } 077 078 public void distributeStateMessage(LoaderState state, Object... eventData) 079 { 080 if (state.hasEvent()) 081 { 082 masterChannel.post(state.getEvent(eventData)); 083 } 084 } 085 086 public void transition(LoaderState desiredState) 087 { 088 LoaderState oldState = state; 089 state = state.transition(!errors.isEmpty()); 090 if (state != desiredState) 091 { 092 Throwable toThrow = null; 093 FMLLog.severe("Fatal errors were detected during the transition from %s to %s. Loading cannot continue", oldState, desiredState); 094 StringBuilder sb = new StringBuilder(); 095 printModStates(sb); 096 FMLLog.severe(sb.toString()); 097 FMLLog.severe("The following problems were captured during this phase"); 098 for (Entry<String, Throwable> error : errors.entries()) 099 { 100 FMLLog.log(Level.SEVERE, error.getValue(), "Caught exception from %s", error.getKey()); 101 if (error.getValue() instanceof IFMLHandledException) 102 { 103 toThrow = error.getValue(); 104 } 105 else if (toThrow == null) 106 { 107 toThrow = error.getValue(); 108 } 109 } 110 if (toThrow != null && toThrow instanceof RuntimeException) 111 { 112 throw (RuntimeException)toThrow; 113 } 114 else 115 { 116 throw new LoaderException(toThrow); 117 } 118 } 119 } 120 121 public ModContainer activeContainer() 122 { 123 return activeContainer; 124 } 125 126 @Subscribe 127 public void propogateStateMessage(FMLStateEvent stateEvent) 128 { 129 if (stateEvent instanceof FMLPreInitializationEvent) 130 { 131 modObjectList = buildModObjectList(); 132 } 133 for (ModContainer mc : activeModList) 134 { 135 activeContainer = mc; 136 String modId = mc.getModId(); 137 stateEvent.applyModContainer(activeContainer()); 138 FMLLog.finer("Posting state event %s to mod %s", stateEvent.getEventType(), modId); 139 eventChannels.get(modId).post(stateEvent); 140 FMLLog.finer("State event %s delivered to mod %s", stateEvent.getEventType(), modId); 141 activeContainer = null; 142 if (!errors.containsKey(modId)) 143 { 144 modStates.put(modId, stateEvent.getModState()); 145 } 146 else 147 { 148 modStates.put(modId, ModState.ERRORED); 149 } 150 } 151 } 152 153 public ImmutableBiMap<ModContainer, Object> buildModObjectList() 154 { 155 ImmutableBiMap.Builder<ModContainer, Object> builder = ImmutableBiMap.<ModContainer, Object>builder(); 156 for (ModContainer mc : activeModList) 157 { 158 if (!mc.isImmutable() && mc.getMod()!=null) 159 { 160 builder.put(mc, mc.getMod()); 161 } 162 if (mc.getMod()==null && !mc.isImmutable() && state!=LoaderState.CONSTRUCTING) 163 { 164 FMLLog.severe("There is a severe problem with %s - it appears not to have constructed correctly", mc.getModId()); 165 if (state != LoaderState.CONSTRUCTING) 166 { 167 this.errorOccurred(mc, new RuntimeException()); 168 } 169 } 170 } 171 return builder.build(); 172 } 173 174 public void errorOccurred(ModContainer modContainer, Throwable exception) 175 { 176 if (exception instanceof InvocationTargetException) 177 { 178 errors.put(modContainer.getModId(), ((InvocationTargetException)exception).getCause()); 179 } 180 else 181 { 182 errors.put(modContainer.getModId(), exception); 183 } 184 } 185 186 public void printModStates(StringBuilder ret) 187 { 188 for (ModContainer mc : loader.getModList()) 189 { 190 ret.append("\n\t").append(mc.getModId()).append(" [").append(mc.getName()).append("] (").append(mc.getSource().getName()).append(") "); 191 Joiner.on("->"). appendTo(ret, modStates.get(mc.getModId())); 192 } 193 } 194 195 public List<ModContainer> getActiveModList() 196 { 197 return activeModList; 198 } 199 200 public ModState getModState(ModContainer selectedMod) 201 { 202 return Iterables.getLast(modStates.get(selectedMod.getModId()), ModState.AVAILABLE); 203 } 204 205 public void distributeStateMessage(Class<?> customEvent) 206 { 207 try 208 { 209 masterChannel.post(customEvent.newInstance()); 210 } 211 catch (Exception e) 212 { 213 FMLLog.log(Level.SEVERE, e, "An unexpected exception"); 214 throw new LoaderException(e); 215 } 216 } 217 218 public BiMap<ModContainer, Object> getModObjectList() 219 { 220 if (modObjectList == null) 221 { 222 FMLLog.severe("Detected an attempt by a mod %s to perform game activity during mod construction. This is a serious programming error.", activeContainer); 223 return buildModObjectList(); 224 } 225 return ImmutableBiMap.copyOf(modObjectList); 226 } 227 228 public boolean isInState(LoaderState state) 229 { 230 return this.state == state; 231 } 232 }