2018-06-15 15:03:35 -04:00
/*
* Minecraft Forge
2020-07-02 19:49:11 +02:00
* Copyright (c) 2016-2020.
2018-06-15 15:03:35 -04:00
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package net.minecraftforge.fml ;
2019-03-11 23:32:37 +01:00
import com.google.common.collect.ImmutableList ;
2019-01-04 11:44:57 -05:00
import cpw.mods.modlauncher.TransformingClassLoader ;
2018-06-21 15:37:32 -04:00
import net.minecraftforge.api.distmarker.Dist ;
2018-06-19 14:04:05 -04:00
import net.minecraftforge.common.capabilities.CapabilityManager ;
2019-06-22 20:36:07 -04:00
import net.minecraftforge.eventbus.api.Event ;
2019-01-28 22:42:37 -05:00
import net.minecraftforge.fml.config.ConfigTracker ;
import net.minecraftforge.fml.config.ModConfig ;
2020-08-30 21:39:06 -04:00
import net.minecraftforge.fml.event.lifecycle.IModBusEvent ;
2018-06-15 15:03:35 -04:00
import net.minecraftforge.fml.loading.FMLLoader ;
2019-02-16 10:46:21 +02:00
import net.minecraftforge.fml.loading.FMLPaths ;
2018-06-15 15:03:35 -04:00
import net.minecraftforge.fml.loading.LoadingModList ;
2019-06-22 04:00:56 +02:00
import net.minecraftforge.fml.loading.moddiscovery.InvalidModIdentifier ;
2018-06-15 15:03:35 -04:00
import net.minecraftforge.fml.loading.moddiscovery.ModFile ;
import net.minecraftforge.fml.loading.moddiscovery.ModFileInfo ;
2019-10-05 17:02:54 -04:00
import net.minecraftforge.fml.loading.progress.StartupMessageManager ;
2019-03-04 08:22:46 -05:00
import net.minecraftforge.fml.network.FMLNetworkConstants ;
2019-03-18 00:01:23 +01:00
import net.minecraftforge.fml.network.NetworkRegistry ;
2019-02-16 10:46:21 +02:00
import net.minecraftforge.forgespi.language.IModInfo ;
2019-02-10 11:44:47 -05:00
import net.minecraftforge.forgespi.language.IModLanguageProvider ;
2018-06-19 14:04:05 -04:00
import net.minecraftforge.registries.GameData ;
2019-01-09 20:57:01 -08:00
import net.minecraftforge.registries.ObjectHolderRegistry ;
2019-07-26 20:02:21 -04:00
import net.minecraftforge.versions.forge.ForgeVersion ;
2018-06-15 15:03:35 -04:00
import org.apache.logging.log4j.LogManager ;
import org.apache.logging.log4j.Logger ;
2020-10-04 16:40:38 -04:00
import java.util.* ;
2020-08-30 21:39:06 -04:00
import java.util.concurrent.CompletableFuture ;
import java.util.concurrent.CompletionException ;
2019-06-23 12:26:25 -04:00
import java.util.concurrent.Executor ;
2019-06-22 23:39:00 -04:00
import java.util.function.Consumer ;
2018-06-15 15:03:35 -04:00
import java.util.function.Function ;
import java.util.stream.Collectors ;
2018-06-21 15:37:32 -04:00
import static net.minecraftforge.fml.Logging.CORE ;
2019-01-04 11:44:57 -05:00
import static net.minecraftforge.fml.Logging.LOADING ;
2018-06-15 15:03:35 -04:00
2019-01-14 22:32:43 -05:00
/**
* Loads mods.
*
* Dispatch cycle is seen in {@link #loadMods()} and {@link #finishMods()}
*
* Overall sequence for loadMods is:
* <dl>
* <dt>CONSTRUCT</dt>
* <dd>Constructs the mod instance. Mods can typically setup basic environment such as Event listeners
* and Configuration specifications here.</dd>
* <dt>Automated dispatches</dt>
* <dd>Dispatches automated elements : {@link net.minecraftforge.fml.common.Mod.EventBusSubscriber},
* {@link net.minecraftforge.event.RegistryEvent}, {@link net.minecraftforge.common.capabilities.CapabilityInject}
* and others</dd>
* <dt>CONFIG_LOAD</dt>
* <dd>Dispatches ConfigLoadEvent to mods</dd>
* <dt>COMMON_SETUP</dt>
* <dd>Dispatches {@link net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent} to mods</dd>
* <dt>SIDED_SETUP</dt>
* <dd>Dispatches {@link net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent} or
* {@link net.minecraftforge.fml.event.lifecycle.FMLDedicatedServerSetupEvent} to mods</dd>
* </dl>
*
* Overall sequence for finishMods is:
* <dl>
* <dt>ENQUEUE_IMC</dt>
* <dd>Dispatches {@link net.minecraftforge.fml.event.lifecycle.InterModEnqueueEvent} to mods,
* for enqueuing {@link InterModComms} messages for other mods to receive subsequently</dd>
* <dt>PROCESS_IMC</dt>
* <dd>Dispatches {@link net.minecraftforge.fml.event.lifecycle.InterModProcessEvent} to mods,
* for processing {@link InterModComms} messages received from other mods prior to this event</dd>
* <dt>COMPLETE</dt>
* <dd>Dispatches {@link net.minecraftforge.fml.event.lifecycle.FMLLoadCompleteEvent} to mods,
* and completes the mod loading sequence.</dd>
* </dl>
*/
2018-06-15 15:03:35 -04:00
public class ModLoader
{
2018-08-27 13:10:07 -04:00
private static final Logger LOGGER = LogManager . getLogger ();
2018-06-15 15:03:35 -04:00
private static ModLoader INSTANCE ;
2019-01-04 11:44:57 -05:00
private final TransformingClassLoader launchClassLoader ;
2018-06-15 15:03:35 -04:00
private final LoadingModList loadingModList ;
2018-09-28 21:07:46 -04:00
private final List < ModLoadingException > loadingExceptions ;
2019-03-11 23:32:37 +01:00
private final List < ModLoadingWarning > loadingWarnings ;
2020-08-31 21:00:44 -04:00
private boolean loadingStateValid ;
2019-06-22 23:39:00 -04:00
@SuppressWarnings ( "OptionalUsedAsFieldOrParameterType" )
2019-06-30 23:48:54 -04:00
private final Optional < Consumer < String >> statusConsumer = StartupMessageManager . modLoaderConsumer ();
2019-06-15 21:24:45 -04:00
2018-06-15 15:03:35 -04:00
private ModLoader ()
{
INSTANCE = this ;
this . launchClassLoader = FMLLoader . getLaunchClassLoader ();
this . loadingModList = FMLLoader . getLoadingModList ();
2018-09-28 21:07:46 -04:00
this . loadingExceptions = FMLLoader . getLoadingModList ().
2018-10-04 00:57:08 -04:00
getErrors (). stream (). flatMap ( ModLoadingException :: fromEarlyException ). collect ( Collectors . toList ());
2019-03-11 23:32:37 +01:00
this . loadingWarnings = FMLLoader . getLoadingModList ().
2019-06-22 04:00:56 +02:00
getBrokenFiles (). stream (). map ( file -> new ModLoadingWarning ( null , ModLoadingStage . VALIDATE ,
InvalidModIdentifier . identifyJarProblem ( file . getFilePath ()). orElse ( "fml.modloading.brokenfile" ), file . getFileName ())). collect ( Collectors . toList ());
2020-09-23 23:16:36 +02:00
FMLLoader . getLoadingModList ()
. getModFiles (). stream (). filter ( ModFileInfo :: missingLicense ) //Search for files with missing licenses
. filter ( modFileInfo -> modFileInfo . getMods (). stream (). noneMatch ( thisModInfo -> this . loadingExceptions . stream (). map ( ModLoadingException :: getModInfo ). anyMatch ( otherInfo -> otherInfo == thisModInfo ))) //Ignore files where any other mod already encountered an error
. map ( modFileInfo -> new ModLoadingException ( null , ModLoadingStage . VALIDATE , "fml.modloading.missinglicense" , null , modFileInfo . getFile ()))
. forEach ( this . loadingExceptions :: add );
2019-08-31 13:15:07 -04:00
LOGGER . debug ( CORE , "Loading Network data for FML net version: {}" , FMLNetworkConstants . init ());
2019-07-26 20:02:21 -04:00
CrashReportExtender . registerCrashCallable ( "ModLauncher" , FMLLoader :: getLauncherInfo );
CrashReportExtender . registerCrashCallable ( "ModLauncher launch target" , FMLLoader :: launcherHandlerName );
CrashReportExtender . registerCrashCallable ( "ModLauncher naming" , FMLLoader :: getNaming );
CrashReportExtender . registerCrashCallable ( "ModLauncher services" , this :: computeModLauncherServiceList );
CrashReportExtender . registerCrashCallable ( "FML" , ForgeVersion :: getSpec );
CrashReportExtender . registerCrashCallable ( "Forge" , () -> ForgeVersion . getGroup () + ":" + ForgeVersion . getVersion ());
CrashReportExtender . registerCrashCallable ( "FML Language Providers" , this :: computeLanguageList );
}
private String computeLanguageList () {
return "\n" + FMLLoader . getLanguageLoadingProvider (). applyForEach ( lp -> lp . name () + "@" + lp . getClass (). getPackage (). getImplementationVersion ()). collect ( Collectors . joining ( "\n\t\t" , "\t\t" , "" ));
}
private String computeModLauncherServiceList () {
final List < Map < String , String >> mods = FMLLoader . modLauncherModList ();
return "\n" + mods . stream (). map ( mod -> mod . getOrDefault ( "file" , "nofile" ) +
" " + mod . getOrDefault ( "name" , "missing" ) +
" " + mod . getOrDefault ( "type" , "NOTYPE" ) +
" " + mod . getOrDefault ( "description" , "" )).
collect ( Collectors . joining ( "\n\t\t" , "\t\t" , "" ));
2018-06-15 15:03:35 -04:00
}
public static ModLoader get ()
{
return INSTANCE == null ? INSTANCE = new ModLoader () : INSTANCE ;
}
2020-08-30 21:39:06 -04:00
/**
* Run on the primary starting thread by ClientModLoader and ServerModLoader
* @param syncExecutor An executor to run tasks on the main thread
* @param parallelExecutor An executor to run tasks on a parallel loading thread pool
* @param periodicTask Optional periodic task to perform on the main thread while other activities run
*/
public void gatherAndInitializeMods ( final ModWorkManager . DrivenExecutor syncExecutor , final Executor parallelExecutor , final Runnable periodicTask ) {
2020-08-31 21:00:44 -04:00
loadingStateValid = true ;
2020-04-26 21:03:43 -04:00
statusConsumer . ifPresent ( c -> c . accept ( "Waiting for scan to complete" ));
2020-08-30 21:39:06 -04:00
FMLLoader . backgroundScanHandler . waitForScanToComplete ( periodicTask );
2019-06-22 23:39:00 -04:00
statusConsumer . ifPresent ( c -> c . accept ( "Loading mods" ));
2020-08-30 21:39:06 -04:00
final ModList modList = ModList . of ( loadingModList
. getModFiles ()
. stream ()
. map ( ModFileInfo :: getFile )
. collect ( Collectors . toList ()),
loadingModList . getMods ());
2018-10-04 00:57:08 -04:00
if ( ! this . loadingExceptions . isEmpty ()) {
2019-06-23 14:37:05 -04:00
LOGGER . fatal ( CORE , "Error during pre-loading phase" , loadingExceptions . get ( 0 ));
modList . setLoadedMods ( Collections . emptyList ());
2020-08-31 21:00:44 -04:00
loadingStateValid = false ;
2018-10-04 00:57:08 -04:00
throw new LoadingFailedException ( loadingExceptions );
}
2019-06-22 23:39:00 -04:00
statusConsumer . ifPresent ( c -> c . accept ( "Building Mod List" ));
2019-02-16 10:46:21 +02:00
final List < ModContainer > modContainers = loadingModList . getModFiles (). stream ().
2018-06-15 15:03:35 -04:00
map ( ModFileInfo :: getFile ).
2019-01-06 15:46:29 -05:00
map ( mf -> buildMods ( mf , launchClassLoader )).
2019-02-16 10:46:21 +02:00
flatMap ( Collection :: stream ).
collect ( Collectors . toList ());
2018-09-28 21:07:46 -04:00
if ( ! loadingExceptions . isEmpty ()) {
2019-06-23 14:37:05 -04:00
LOGGER . fatal ( CORE , "Failed to initialize mod containers" , loadingExceptions . get ( 0 ));
modList . setLoadedMods ( Collections . emptyList ());
2020-08-31 21:00:44 -04:00
loadingStateValid = false ;
2018-09-28 21:07:46 -04:00
throw new LoadingFailedException ( loadingExceptions );
}
2019-02-16 10:46:21 +02:00
modList . setLoadedMods ( modContainers );
2019-06-30 23:48:54 -04:00
statusConsumer . ifPresent ( c -> c . accept ( String . format ( "Constructing %d mods" , modList . size ())));
2020-08-30 21:39:06 -04:00
dispatchAndHandleError ( ModLoadingStage . CONSTRUCT , syncExecutor , parallelExecutor , periodicTask );
2019-06-22 23:39:00 -04:00
statusConsumer . ifPresent ( c -> c . accept ( "Creating registries" ));
2020-08-30 21:39:06 -04:00
dispatchAndHandleError ( ModLoadingStage . CREATE_REGISTRIES , syncExecutor , parallelExecutor , periodicTask );
2019-01-09 20:57:01 -08:00
ObjectHolderRegistry . findObjectHolders ();
2018-06-19 14:04:05 -04:00
CapabilityManager . INSTANCE . injectCapabilities ( modList . getAllScanData ());
2020-09-08 22:10:36 -04:00
statusConsumer . ifPresent ( c -> c . accept ( "Adding custom tag types" ));
GameData . setCustomTagTypesFromRegistries ();
2020-09-23 23:12:11 -04:00
statusConsumer . ifPresent ( c -> c . accept ( "Populating registries" ));
dispatchAndHandleError ( ModLoadingStage . LOAD_REGISTRIES , syncExecutor , parallelExecutor , periodicTask );
2019-06-22 23:39:00 -04:00
statusConsumer . ifPresent ( c -> c . accept ( "Early mod loading complete" ));
2018-09-28 21:07:46 -04:00
}
2020-08-30 21:39:06 -04:00
public void loadMods ( final ModWorkManager . DrivenExecutor syncExecutor , final Executor parallelExecutor , final Function < Executor , CompletableFuture < Void >> beforeSidedEvent , final Function < Executor , CompletableFuture < Void >> afterSidedEvent , final Runnable periodicTask ) {
statusConsumer . ifPresent ( c -> c . accept ( "Loading mod config" ));
DistExecutor . unsafeRunWhenOn ( Dist . CLIENT , () -> () -> ConfigTracker . INSTANCE . loadConfigs ( ModConfig . Type . CLIENT , FMLPaths . CONFIGDIR . get ()));
ConfigTracker . INSTANCE . loadConfigs ( ModConfig . Type . COMMON , FMLPaths . CONFIGDIR . get ());
statusConsumer . ifPresent ( c -> c . accept ( "Mod setup: SETUP" ));
dispatchAndHandleError ( ModLoadingStage . COMMON_SETUP , syncExecutor , parallelExecutor , periodicTask );
statusConsumer . ifPresent ( c -> c . accept ( "Mod setup: SIDED SETUP" ));
dispatchAndHandleError ( ModLoadingStage . SIDED_SETUP , syncExecutor , parallelExecutor , periodicTask , beforeSidedEvent , afterSidedEvent );
statusConsumer . ifPresent ( c -> c . accept ( "Mod setup complete" ));
}
public void finishMods ( final ModWorkManager . DrivenExecutor syncExecutor , final Executor parallelExecutor , final Runnable periodicTask )
{
statusConsumer . ifPresent ( c -> c . accept ( "Mod setup: ENQUEUE IMC" ));
dispatchAndHandleError ( ModLoadingStage . ENQUEUE_IMC , syncExecutor , parallelExecutor , periodicTask );
statusConsumer . ifPresent ( c -> c . accept ( "Mod setup: PROCESS IMC" ));
dispatchAndHandleError ( ModLoadingStage . PROCESS_IMC , syncExecutor , parallelExecutor , periodicTask );
statusConsumer . ifPresent ( c -> c . accept ( "Mod setup: Final completion" ));
dispatchAndHandleError ( ModLoadingStage . COMPLETE , syncExecutor , parallelExecutor , periodicTask );
statusConsumer . ifPresent ( c -> c . accept ( "Freezing data" ));
GameData . freezeData ();
NetworkRegistry . lock ();
statusConsumer . ifPresent ( c -> c . accept ( String . format ( "Mod loading complete - %d mods loaded" , ModList . get (). size ())));
}
private void dispatchAndHandleError ( ModLoadingStage state , ModWorkManager . DrivenExecutor syncExecutor , Executor parallelExecutor , final Runnable ticker ) {
2020-08-31 21:00:44 -04:00
if ( ! isLoadingStateValid ()) {
LOGGER . error ( "Cowardly refusing to process mod state change request from {}" , state );
return ;
}
2020-08-30 21:39:06 -04:00
waitForTransition ( state , syncExecutor , ticker , state . buildTransition ( syncExecutor , parallelExecutor ));
}
private void dispatchAndHandleError ( ModLoadingStage state , ModWorkManager . DrivenExecutor syncExecutor , Executor parallelExecutor , final Runnable ticker , Function < Executor , CompletableFuture < Void >> preSyncTask , Function < Executor , CompletableFuture < Void >> postSyncTask ) {
2020-08-31 21:00:44 -04:00
if ( ! isLoadingStateValid ()) {
LOGGER . error ( "Cowardly refusing to process mod state change request from {}" , state );
return ;
}
2020-08-30 21:39:06 -04:00
waitForTransition ( state , syncExecutor , ticker , state . buildTransition ( syncExecutor , parallelExecutor , preSyncTask , postSyncTask ));
}
private void waitForTransition ( final ModLoadingStage state , final ModWorkManager . DrivenExecutor syncExecutor , final Runnable ticker , final CompletableFuture < List < Throwable >> transition ) {
while ( ! transition . isDone ()) {
2020-08-31 21:00:44 -04:00
syncExecutor . drive ( ticker );
2018-09-28 21:07:46 -04:00
}
2020-08-30 21:39:06 -04:00
try {
transition . join ();
} catch ( CompletionException e ) {
2020-08-31 21:00:44 -04:00
loadingStateValid = false ;
2020-08-30 21:39:06 -04:00
Throwable t = e . getCause ();
final List < Throwable > notModLoading = Arrays . stream ( t . getSuppressed ())
. filter ( obj -> ! ( obj instanceof ModLoadingException ))
. collect ( Collectors . toList ());
if ( ! notModLoading . isEmpty ()) {
LOGGER . fatal ( "Encountered non-modloading exceptions!" , e );
throw e ;
}
final List < ModLoadingException > modLoadingExceptions = Arrays . stream ( t . getSuppressed ())
. filter ( ModLoadingException . class :: isInstance )
. map ( ModLoadingException . class :: cast )
. collect ( Collectors . toList ());
LOGGER . fatal ( LOADING , "Failed to complete lifecycle event {}, {} errors found" , state , modLoadingExceptions . size ());
throw new LoadingFailedException ( modLoadingExceptions );
2018-09-28 21:07:46 -04:00
}
}
2018-06-15 15:03:35 -04:00
2019-01-06 15:46:29 -05:00
private List < ModContainer > buildMods ( final ModFile modFile , final TransformingClassLoader modClassLoader )
2018-06-15 15:03:35 -04:00
{
final Map < String , IModInfo > modInfoMap = modFile . getModFileInfo (). getMods (). stream (). collect ( Collectors . toMap ( IModInfo :: getModId , Function . identity ()));
2019-01-04 11:44:57 -05:00
LOGGER . debug ( LOADING , "ModContainer is {}" , ModContainer . class . getClassLoader ());
2019-05-11 20:47:27 -04:00
final List < ModContainer > containers = modFile . getScanResult (). getTargets (). entrySet (). stream ().
2019-08-03 19:25:41 +02:00
map ( e -> buildModContainerFromTOML ( modFile , modClassLoader , modInfoMap , e ))
2020-08-30 21:39:06 -04:00
. filter ( Objects :: nonNull )
2019-08-03 19:25:41 +02:00
. collect ( Collectors . toList ());
2019-05-11 20:47:27 -04:00
if ( containers . size () != modInfoMap . size ()) {
LOGGER . fatal ( LOADING , "File {} constructed {} mods: {}, but had {} mods specified: {}" ,
modFile . getFilePath (),
2020-06-03 16:57:07 -07:00
containers . size (), containers . stream (). map ( c -> c != null ? c . getModId () : "(null)" ). sorted (). collect ( Collectors . toList ()),
modInfoMap . size (), modInfoMap . values (). stream (). map ( IModInfo :: getModId ). sorted (). collect ( Collectors . toList ()));
2019-05-11 20:47:27 -04:00
loadingExceptions . add ( new ModLoadingException ( null , ModLoadingStage . CONSTRUCT , "fml.modloading.missingclasses" , null , modFile . getFilePath ()));
}
return containers ;
2019-02-10 11:44:47 -05:00
}
private ModContainer buildModContainerFromTOML ( final ModFile modFile , final TransformingClassLoader modClassLoader , final Map < String , IModInfo > modInfoMap , final Map . Entry < String , ? extends IModLanguageProvider . IModLanguageLoader > idToProviderEntry ) {
try {
final String modId = idToProviderEntry . getKey ();
final IModLanguageProvider . IModLanguageLoader languageLoader = idToProviderEntry . getValue ();
IModInfo info = Optional . ofNullable ( modInfoMap . get ( modId )).
// throw a missing metadata error if there is no matching modid in the modInfoMap from the mods.toml file
orElseThrow (() -> new ModLoadingException ( null , ModLoadingStage . CONSTRUCT , "fml.modloading.missingmetadata" , null , modId ));
return languageLoader . loadMod ( info , modClassLoader , modFile . getScanResult ());
} catch ( ModLoadingException mle ) {
// exceptions are caught and added to the error list for later handling. Null is returned here.
loadingExceptions . add ( mle );
return null ;
}
2018-06-15 15:03:35 -04:00
}
2020-08-31 21:00:44 -04:00
/**
* @return If the current mod loading state is valid. Use if you interact with vanilla systems directly during loading
* and don't want to cause extraneous crashes due to trying to do things that aren't possible in a "broken load"
*/
public static boolean isLoadingStateValid () {
return get (). loadingStateValid ;
}
public < T extends Event & IModBusEvent > void runEventGenerator ( Function < ModContainer , T > generator ) {
if ( ! loadingStateValid ) {
LOGGER . error ( "Cowardly refusing to send event generator to a broken mod state" );
return ;
}
ModList . get (). forEachModContainer (( id , mc ) -> mc . acceptEvent ( generator . apply ( mc )));
}
2020-08-30 21:39:06 -04:00
public < T extends Event & IModBusEvent > void postEvent ( T e ) {
2020-08-31 21:00:44 -04:00
if ( ! loadingStateValid ) {
LOGGER . error ( "Cowardly refusing to send event {} to a broken mod state" , e . getClass (). getName ());
return ;
}
2019-06-22 20:36:07 -04:00
ModList . get (). forEachModContainer (( id , mc ) -> mc . acceptEvent ( e ));
}
2019-03-11 23:32:37 +01:00
public List < ModLoadingWarning > getWarnings ()
{
return ImmutableList . copyOf ( this . loadingWarnings );
}
public void addWarning ( ModLoadingWarning warning )
{
this . loadingWarnings . add ( warning );
}
2019-06-15 21:24:45 -04:00
2020-06-29 19:39:04 -04:00
private static boolean runningDataGen = false ;
public static boolean isDataGenRunning () {
return runningDataGen ;
}
2018-06-15 15:03:35 -04:00
}