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 }