    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}