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