Skip to content

Commit

Permalink
Improve loading error screen message formatting
Browse files Browse the repository at this point in the history
  • Loading branch information
Su5eD committed Aug 15, 2023
1 parent 26da15d commit 0816780
Show file tree
Hide file tree
Showing 9 changed files with 108 additions and 68 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@
import net.fabricmc.loader.api.entrypoint.PreLaunchEntrypoint;
import net.fabricmc.loader.impl.FabricLoaderImpl;
import net.fabricmc.loader.impl.entrypoint.EntrypointUtils;
import net.minecraftforge.fml.loading.EarlyLoadingException;
import net.minecraftforge.fml.loading.LoadingModList;
import net.minecraftforge.fml.loading.moddiscovery.ModInfo;
import net.minecraftforge.fml.loading.progress.ProgressMeter;
import net.minecraftforge.fml.loading.progress.StartupNotificationManager;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
Expand All @@ -22,7 +23,7 @@ public class ConnectorEarlyLoader {
private static final Set<String> CONNECTOR_MODS = new HashSet<>();
// If we encounter an exception during setup/load, we store it here and throw it later during FML mod loading,
// so that it is propagated to the forge error screen.
private static Throwable loadingException;
private static final List<EarlyLoadingException> LOADING_EXCEPTIONS = new ArrayList<>();

/**
* @param modid the mod id to look up
Expand All @@ -33,32 +34,51 @@ public static boolean isConnectorMod(String modid) {
}

/**
* @return a suppressed exception if one was encountered during setup/load, otherwise {@code null}
* @return Suppressed exceptions that were encountered during setup/load
*/
@Nullable
public static Throwable getLoadingException() {
return loadingException;
public static List<EarlyLoadingException> getLoadingExceptions() {
return LOADING_EXCEPTIONS;
}

public static void addSuppressed(Throwable t) {
if (loadingException == null) {
loadingException = t;
}
else {
loadingException.addSuppressed(t);
/**
* @return Whether a loading exception has been encountered up to this point in loading
*/
public static boolean hasEncounteredException() {
return !LOADING_EXCEPTIONS.isEmpty() || LoadingModList.get() != null && !LoadingModList.get().getErrors().isEmpty();
}

/**
* Nicely wraps the exception message in a color coded format for easier readability.
*
* @param t the encountered exception
* @param message simple error message to show
*/
public static void addGenericLoadingException(Throwable t, String message) {
EarlyLoadingException exception = createGenericLoadingException(t, message);
if (LoadingModList.get() != null) {
LoadingModList.get().getErrors().add(exception);
} else {
LOADING_EXCEPTIONS.add(exception);
}
}

public static EarlyLoadingException createGenericLoadingException(Throwable original, String message) {
return createLoadingException(original, "§e[Connector]§r {3}\n§c{4}§7: {5}§r", message, original.getClass().getName(), original.getMessage());
}

public static EarlyLoadingException createLoadingException(Throwable original, String message, Object... args) {
return new EarlyLoadingException(original.getMessage(), original, List.of(new EarlyLoadingException.ExceptionData(message, args)));
}

/**
* Run initial fabric loader setup and invoke preLaunch entrypoint. Any exceptions thrown are ignored and thrown
* later during FML load.
*
* @see #CONNECTOR_MODS
* @see #loadingException
*/
@SuppressWarnings("unused")
public static void setup() {
if (getLoadingException() != null) {
if (hasEncounteredException()) {
LOGGER.error("Skipping early mod setup due to previous error");
return;
}
Expand All @@ -81,7 +101,7 @@ public static void setup() {
EntrypointUtils.invoke("preLaunch", PreLaunchEntrypoint.class, PreLaunchEntrypoint::onPreLaunch);
} catch (Throwable t) {
LOGGER.error("Encountered error during early mod setup", t);
addSuppressed(t);
addGenericLoadingException(t, "Encountered an error during early mod setup");
}
progress.complete();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,11 @@ public String getType() {
@Override
public Collection<String> getProvides() {
Set<String> provides = new HashSet<>(this.wrapped.getProvides());
provides.add(this.wrapped.getId()); // Add original modid to mod lookup
String original = this.wrapped.getId();
if (!getId().equals(original)) {
// Add original modid to mod lookup
provides.add(original);
}
return provides;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ public Stream<Path> scanCandidates() {
dependencyLocatorList.sort(Comparator.comparingInt(loc -> loc instanceof ConnectorLocator ? 1 : 0));
} catch (Throwable t) {
LOGGER.error("Error sorting FML dependency locators", t);
ConnectorEarlyLoader.addSuppressed(t);
// We can't throw here as that would prevent the connector mod from loading and lead to fabric loader being loaded twice instead
ConnectorEarlyLoader.addGenericLoadingException(t, "Error sorting FML dependency locators");
}
injectLogMarkers();

Expand All @@ -70,7 +71,6 @@ private static void injectLogMarkers() {

// Add a marker filter to the logger's configuration
config.addFilter(MarkerFilter.createFilter("MIXINPATCH", parseLogMarker("connector.logging.marker.mixinpatch"), Filter.Result.NEUTRAL));
config.addFilter(MarkerFilter.createFilter("MERGER", parseLogMarker("connector.logging.marker.merger"), Filter.Result.NEUTRAL));

// Reconfigure the logger with the updated configuration
logger.getContext().updateLoggers();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import dev.su5ed.sinytra.connector.loader.ConnectorLoaderModMetadata;
import dev.su5ed.sinytra.connector.transformer.JarTransformer;
import net.fabricmc.loader.impl.metadata.NestedJarEntry;
import net.minecraftforge.fml.loading.EarlyLoadingException;
import net.minecraftforge.fml.loading.FMLPaths;
import net.minecraftforge.fml.loading.ModDirTransformerDiscoverer;
import net.minecraftforge.fml.loading.StringUtils;
Expand Down Expand Up @@ -53,6 +54,23 @@ public class ConnectorLocator extends AbstractJarFileModProvider implements IDep

@Override
public List<IModFile> scanMods(Iterable<IModFile> loadedMods) {
if (ConnectorEarlyLoader.hasEncounteredException()) {
LOGGER.error("Skipping mod scan due to previously encountered error");
return List.of();
}
try {
return locateFabricMods(loadedMods);
} catch (EarlyLoadingException e) {
// Let these pass through
throw e;
} catch (Throwable t) {
// Rethrow other exceptions
StartupNotificationManager.addModMessage("CONNECTOR LOCATOR ERROR");
throw ConnectorEarlyLoader.createGenericLoadingException(t, "Fabric mod discovery failed");
}
}

private List<IModFile> locateFabricMods(Iterable<IModFile> loadedMods) {
LOGGER.debug(SCAN, "Scanning mods dir {} for mods", FMLPaths.MODSDIR.get());
List<Path> excluded = ModDirTransformerDiscoverer.allExcluded();
Path tempDir = ConnectorUtil.CONNECTOR_FOLDER.resolve("temp");
Expand Down Expand Up @@ -80,22 +98,22 @@ public List<IModFile> scanMods(Iterable<IModFile> loadedMods) {
return discoverNestedJarsRecursive(tempDir, secureJar, metadata.getJars());
})
.toList();
// Get renamer library classpath
List<Path> renameLibs = StreamSupport.stream(loadedMods.spliterator(), false).map(modFile -> modFile.getSecureJar().getRootPath()).toList();
// Remove duplicates and existing mods
List<JarTransformer.TransformableJar> uniqueNestedJars = handleDuplicateMods(Objects.requireNonNull(discoveredNestedJars), loadedModIds);
// Merge outer and nested jar lists
List<JarTransformer.TransformableJar> allJars = Stream.concat(discoveredJars.stream(), uniqueNestedJars.stream()).toList();
// Get renamer library classpath
List<Path> renameLibs = StreamSupport.stream(loadedMods.spliterator(), false).map(modFile -> modFile.getSecureJar().getRootPath()).toList();
// Run jar transformations (or get existing outputs from cache)
List<JarTransformer.FabricModPath> transformed = JarTransformer.transform(allJars, renameLibs);
// Deal with split packages (thanks modules)
List<SplitPackageMerger.FilteredModPath> moduleSafeJars = SplitPackageMerger.mergeSplitPackages(transformed, loadedMods);
// Handle jar transformation errors
if (ConnectorEarlyLoader.getLoadingException() != null) {
// Skip last step to save time if an error occured during transformation
if (ConnectorEarlyLoader.hasEncounteredException()) {
StartupNotificationManager.addModMessage("JAR TRANSFORMATION ERROR");
LOGGER.error("Cancelling Connector jar discovery due to previous error", ConnectorEarlyLoader.getLoadingException());
LOGGER.error("Cancelling jar discovery due to previous error");
return List.of();
}
// Deal with split packages (thanks modules)
List<SplitPackageMerger.FilteredModPath> moduleSafeJars = SplitPackageMerger.mergeSplitPackages(transformed, loadedMods);
return moduleSafeJars.stream()
.map(mod -> createConnectorModFile(mod, this))
.toList();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@
import net.minecraftforge.forgespi.locating.IModFile;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.Marker;
import org.slf4j.MarkerFactory;

import java.nio.file.Path;
import java.util.ArrayList;
Expand All @@ -30,7 +28,6 @@

public class SplitPackageMerger {
private static final Logger LOGGER = LogUtils.getLogger();
private static final Marker MERGER = MarkerFactory.getMarker("MERGER");

/**
* Detect and resolve split package conflicts in jars.
Expand Down Expand Up @@ -61,7 +58,7 @@ public static List<FilteredModPath> mergeSplitPackages(List<FabricModPath> paths
AtomicInteger totalJars = new AtomicInteger(0);
pkgSources.forEach((pkg, sources) -> {
if (sources.size() > 1) {
LOGGER.debug(MERGER, "Found split package {} in jars {}", pkg, sources.stream().map(info -> info.getFirst().name()).collect(Collectors.joining(",")));
LOGGER.debug("Found split package {} in jars {}", pkg, sources.stream().map(info -> info.getFirst().name()).collect(Collectors.joining(",")));
sources.forEach(source -> {
if (plainPaths.remove(source.getSecond())) {
totalJars.getAndIncrement();
Expand All @@ -71,7 +68,7 @@ public static List<FilteredModPath> mergeSplitPackages(List<FabricModPath> paths
});
}
});
LOGGER.debug(MERGER, "Found {} split packages across {} jars", mergePkgs.keySet().size(), totalJars.get());
LOGGER.debug("Found {} split packages across {} jars", mergePkgs.keySet().size(), totalJars.get());

// Name -> Jar merge info
Map<String, JarMergeInfo> jarMap = new HashMap<>();
Expand Down Expand Up @@ -107,7 +104,7 @@ public static List<FilteredModPath> mergeSplitPackages(List<FabricModPath> paths
FabricModPath modPath = pair.getSecond();
plainPaths.remove(modPath);
JarMergeInfo info = jarMap.computeIfAbsent(jar.name(), str -> new JarMergeInfo(jar, modPath.metadata()));
LOGGER.debug(MERGER, "Excluding existing package {} from jar {}", pkg, jar.name());
LOGGER.debug("Excluding existing package {} from jar {}", pkg, jar.name());
info.excludedPackages().add(pkg);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@
import cpw.mods.modlauncher.LaunchPluginHandler;
import cpw.mods.modlauncher.Launcher;
import cpw.mods.modlauncher.api.IEnvironment;
import cpw.mods.modlauncher.api.IModuleLayerManager;
import cpw.mods.modlauncher.api.ITransformationService;
import cpw.mods.modlauncher.api.ITransformer;
import cpw.mods.modlauncher.serviceapi.ILaunchPluginService;
import dev.su5ed.sinytra.connector.loader.ConnectorEarlyLoader;
import net.minecraftforge.fml.loading.LoadingModList;

import java.lang.reflect.Field;
import java.util.List;
Expand Down Expand Up @@ -42,6 +45,12 @@ public void onLoad(IEnvironment env, Set<String> otherServices) {
}
}

@Override
public List<Resource> completeScan(IModuleLayerManager layerManager) {
LoadingModList.get().getErrors().addAll(ConnectorEarlyLoader.getLoadingExceptions());
return List.of();
}

@Override
public List<ITransformer> transformers() {
return List.of();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,39 +176,39 @@ private static List<FabricModPath> transformJars(List<TransformableJar> paths, L

Stopwatch stopwatch = Stopwatch.createStarted();
ProgressMeter progress = StartupNotificationManager.addProgressBar("[Connector] Transforming Jars", paths.size());
ExecutorService executorService = Executors.newFixedThreadPool(paths.size());
ClassProvider classProvider = ClassProvider.fromPaths(libs.toArray(Path[]::new));
Transformer remappingTransformer = RelocatingRenamingTransformer.create(classProvider, s -> {}, FabricLoaderImpl.INSTANCE.getMappingResolver().getCurrentMap(SOURCE_NAMESPACE), getFlatMapping(SOURCE_NAMESPACE));
List<Future<FabricModPath>> futures = paths.stream()
.map(jar -> executorService.submit(() -> {
FabricModPath path = jar.transform(remappingTransformer);
progress.increment();
return path;
}))
.toList();
executorService.shutdown();
try {
ExecutorService executorService = Executors.newFixedThreadPool(paths.size());
ClassProvider classProvider = ClassProvider.fromPaths(libs.toArray(Path[]::new));
Transformer remappingTransformer = RelocatingRenamingTransformer.create(classProvider, s -> {}, FabricLoaderImpl.INSTANCE.getMappingResolver().getCurrentMap(SOURCE_NAMESPACE), getFlatMapping(SOURCE_NAMESPACE));
List<Future<FabricModPath>> futures = paths.stream()
.map(jar -> executorService.submit(() -> {
FabricModPath path = jar.transform(remappingTransformer);
progress.increment();
return path;
}))
.toList();
executorService.shutdown();
if (!executorService.awaitTermination(60, TimeUnit.SECONDS)) {
throw new RuntimeException("Timed out waiting for jar remap");
}
List<FabricModPath> results = futures.stream()
.map(future -> {
try {
return future.get();
} catch (Throwable t) {
throw ConnectorEarlyLoader.createGenericLoadingException(t, "Error transforming jar");
}
})
.filter(Objects::nonNull)
.toList();
stopwatch.stop();
LOGGER.debug(TRANSFORM_MARKER, "Processed all jars in {} ms", stopwatch.elapsed(TimeUnit.MILLISECONDS));
return results;
} catch (InterruptedException ignored) {
// Dunny what I should do with this
return List.of();
} finally {
progress.complete();
}
List<FabricModPath> results = futures.stream()
.map(future -> {
try {
return future.get();
} catch (Throwable t) {
ConnectorEarlyLoader.addSuppressed(t);
return null;
}
})
.filter(Objects::nonNull)
.toList();
progress.complete();
stopwatch.stop();
LOGGER.debug(TRANSFORM_MARKER, "Processed all jars in {} ms", stopwatch.elapsed(TimeUnit.MILLISECONDS));
return results;
}

private static void transformJar(File input, Path output, FabricModFileMetadata metadata, Transformer remappingTransformer) throws IOException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,9 @@ public static boolean isLoading() {
* later during FML load.
*
* @see ConnectorEarlyLoader#CONNECTOR_MODS
* @see ConnectorEarlyLoader#loadingException
*/
public static void load() {
if (ConnectorEarlyLoader.getLoadingException() != null) {
if (ConnectorEarlyLoader.hasEncounteredException()) {
LOGGER.error("Skipping early mod loading due to previous error");
return;
}
Expand All @@ -62,8 +61,7 @@ public static void load() {

loading = false;
} catch (Throwable t) {
LOGGER.error("Encountered error during early mod loading", t);
ConnectorEarlyLoader.addSuppressed(t);
throw ConnectorEarlyLoader.createGenericLoadingException(t, "Encountered error during early mod loading");
}
progress.complete();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package dev.su5ed.sinytra.connector.mod;

import dev.su5ed.sinytra.connector.loader.ConnectorEarlyLoader;
import dev.su5ed.sinytra.connector.mod.compat.FluidHandlerCompat;
import dev.su5ed.sinytra.connector.mod.compat.LazyEntityAttributes;
import net.minecraftforge.eventbus.api.EventPriority;
Expand All @@ -20,11 +19,6 @@ public static boolean clientLoadComplete() {
}

public ConnectorMod() {
Throwable loadingException = ConnectorEarlyLoader.getLoadingException();
if (loadingException != null) {
throw new RuntimeException("Connector early loading failed", loadingException);
}

IEventBus bus = FMLJavaModLoadingContext.get().getModEventBus();
bus.addListener(ConnectorMod::onClientSetup);
ModList modList = ModList.get();
Expand Down

0 comments on commit 0816780

Please sign in to comment.