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.getVersion()).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}