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