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