Skip to content

Commit

Permalink
various patches to optimize loading time (#89)
Browse files Browse the repository at this point in the history
* nuke basemod check

modloader is dead long before 1.7.10 comes to be. This accounts for 3.7 second of loading time.

* optimize ASMDataTable getAnnotationsFor

This accounts for 2.7 second loading time

* spotlessApply (#90)

Co-authored-by: Glease <[email protected]>
Co-authored-by: GitHub GTNH Actions <>

* suppress configuration not found warnings

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
  • Loading branch information
Glease and github-actions[bot] authored Sep 1, 2022
1 parent b0c46b5 commit bf4151d
Show file tree
Hide file tree
Showing 6 changed files with 208 additions and 0 deletions.
6 changes: 6 additions & 0 deletions addon.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
jar {
manifest {
// I need a place to call add this IClassTransformer before CoFHCore loads its transformation target
attributes "CCTransformer": "com.mitchej123.hodgepodge.asm.EarlyClassTransformer"
}
}
35 changes: 35 additions & 0 deletions src/main/java/com/mitchej123/hodgepodge/asm/EarlyASMCallHooks.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.mitchej123.hodgepodge.asm;

import static cpw.mods.fml.common.ModContainerFactory.modTypes;

import cpw.mods.fml.common.FMLLog;
import cpw.mods.fml.common.ModContainer;
import cpw.mods.fml.common.discovery.ModCandidate;
import cpw.mods.fml.common.discovery.asm.ASMModParser;
import cpw.mods.fml.common.discovery.asm.ModAnnotation;
import java.io.File;
import org.apache.logging.log4j.Level;

public class EarlyASMCallHooks {
public static ModContainer build(ASMModParser modParser, File modSource, ModCandidate container) {
String className = modParser.getASMType().getClassName();

for (ModAnnotation ann : modParser.getAnnotations()) {
if (modTypes.containsKey(ann.getASMType())) {
FMLLog.fine("Identified a mod of type %s (%s) - loading", ann.getASMType(), className);
try {
return modTypes.get(ann.getASMType()).newInstance(className, container, ann.getValues());
} catch (Exception e) {
FMLLog.log(
Level.ERROR,
e,
"Unable to construct %s container",
ann.getASMType().getClassName());
return null;
}
}
}

return null;
}
}
106 changes: 106 additions & 0 deletions src/main/java/com/mitchej123/hodgepodge/asm/EarlyClassTransformer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package com.mitchej123.hodgepodge.asm;

import static org.objectweb.asm.Opcodes.*;

import java.io.*;
import java.util.Properties;
import net.minecraft.launchwrapper.IClassTransformer;
import net.minecraft.launchwrapper.Launch;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.objectweb.asm.*;

/**
* This class transformer will be loaded by CodeChickenCore, if it is present.
* It runs much earlier than the rest of class transformers and mixins do and can modify more of forge's and fml's classes.
* <p>
* Due to peculiarity with CCC, and to prevent loading too many classes from messing up the delicate class loading order,
* we will try to minimize the class dependencies on this class, meaning we will not use the usual Configuration class to
* handle configurations
*/
public class EarlyClassTransformer implements IClassTransformer {
private static final boolean noNukeBaseMod;
private static final Logger LOGGER = LogManager.getLogger("HodgePodgeEarly");

static {
Properties config = new Properties();
File configLocation = new File(Launch.minecraftHome, "config/hodgepodgeEarly.properties");
try (Reader r = new BufferedReader(new FileReader(configLocation))) {
config.load(r);
} catch (FileNotFoundException e) {
LOGGER.debug("No existing configuration file. Will use defaults");
} catch (IOException e) {
LOGGER.error("Error reading configuration file. Will use defaults", e);
}
noNukeBaseMod = Boolean.parseBoolean(config.getProperty("noNukeBaseMod"));
config.setProperty("noNukeBaseMod", String.valueOf(noNukeBaseMod));
try (Writer r = new BufferedWriter(new FileWriter(configLocation))) {
config.store(r, "Configuration file for early hodgepodge class transformers");
} catch (IOException e) {
LOGGER.error("Error reading configuration file. Will use defaults", e);
}
}

@Override
public byte[] transform(String name, String transformedName, byte[] basicClass) {
if (basicClass == null || name == null) return basicClass;
switch (name) {
case "cpw.mods.fml.common.ModContainerFactory":
if (noNukeBaseMod) return basicClass;
return transformModContainerFactory(basicClass);
default:
return basicClass;
}
}

private static byte[] transformModContainerFactory(byte[] basicClass) {
ClassReader cr = new ClassReader(basicClass);
ClassWriter cw = new ClassWriter(0);
ClassVisitor cv = new ClassVisitor(ASM5, cw) {
@Override
public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
// we no longer need to check for basemod, so this regex is also useless now. remove it.
if ("modClass".equals(name)) return null;
return super.visitField(access, name, desc, signature, value);
}

@Override
public MethodVisitor visitMethod(
int access, String name, String desc, String signature, String[] exceptions) {
MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
switch (name) {
case "build":
// inject callhook
mv.visitVarInsn(ALOAD, 1);
mv.visitVarInsn(ALOAD, 2);
mv.visitVarInsn(ALOAD, 3);
mv.visitMethodInsn(
INVOKESTATIC,
"com/mitchej123/hodgepodge/asm/EarlyASMCallHooks",
"build",
"(Lcpw/mods/fml/common/discovery/asm/ASMModParser;Ljava/io/File;Lcpw/mods/fml/common/discovery/ModCandidate;)Lcpw/mods/fml/common/ModContainer;",
false);
mv.visitInsn(ARETURN);
mv.visitMaxs(3, 4);
mv.visitEnd();
return null;
case "<clinit>":
mv = new MethodVisitor(ASM5, mv) {
@Override
public void visitFieldInsn(int opcode, String owner, String name, String desc) {
if (name.equals("modClass")) {
// remove access to modClass
super.visitInsn(POP);
} else {
super.visitFieldInsn(opcode, owner, name, desc);
}
}
};
}
return mv;
}
};
cr.accept(cv, 0);
return cw.toByteArray();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ public class LoadingConfig {
public boolean fixPotionIterating;
public boolean fixIgnisFruitAABB;
public boolean fixNetherLeavesFaceRendering;
public boolean optimizeASMDataTable;

// ASM
public boolean pollutionAsm;
Expand Down Expand Up @@ -209,6 +210,12 @@ public LoadingConfig(File file) {
true,
"If fancy graphics are enabled, Nether Leaves render sides with other Nether Leaves adjacent too")
.getBoolean();
optimizeASMDataTable = config.get(
"fixes",
"optimizeASMDataTable",
true,
"Optimize ASMDataTable getAnnotationsFor for faster startup")
.getBoolean();

increaseParticleLimit = config.get("tweaks", "increaseParticleLimit", true, "Increase particle limit")
.getBoolean();
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/com/mitchej123/hodgepodge/mixins/Mixins.java
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ public enum Mixins {
Side.BOTH,
() -> Hodgepodge.config.fixPotionIterating,
TargetedMod.VANILLA),
OPTIMIZE_ASMDATATABLE_INDEX(
"forge.MixinASMDataTable", Side.BOTH, () -> Hodgepodge.config.optimizeASMDataTable, TargetedMod.VANILLA),

// Potentially obsolete vanilla fixes
GRASS_GET_BLOCK_FIX(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package com.mitchej123.hodgepodge.mixins.forge;

import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps;
import com.google.common.collect.SetMultimap;
import cpw.mods.fml.common.ModContainer;
import cpw.mods.fml.common.discovery.ASMDataTable;
import cpw.mods.fml.common.discovery.ASMDataTable.ASMData;
import java.io.File;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Overwrite;
import org.spongepowered.asm.mixin.Shadow;

@Mixin(ASMDataTable.class)
public class MixinASMDataTable {
@Shadow(remap = false)
private SetMultimap<String, ASMData> globalAnnotationData;

@Shadow(remap = false)
private Map<ModContainer, SetMultimap<String, ASMData>> containerAnnotationData;

@Shadow(remap = false)
private List<ModContainer> containers;
/**
* We will forget the guava immutable collections now, since everyone thought they are immutable and won't attempt
* to mutate it.
* @author glee8e
* @reason to optimize the embarrassingly inefficient containerAnnotationData build process
*/
@Overwrite(remap = false)
public SetMultimap<String, ASMData> getAnnotationsFor(ModContainer container) {
if (containerAnnotationData == null) {
Map<ModContainer, SetMultimap<String, ASMData>> mapBuilder = new HashMap<>();
Multimap<File, ModContainer> containersMap = Multimaps.index(containers, ModContainer::getSource);
for (Entry<String, ASMData> entry : globalAnnotationData.entries()) {
for (ModContainer modContainer :
containersMap.get(entry.getValue().getCandidate().getModContainer())) {
mapBuilder
.computeIfAbsent(modContainer, map -> HashMultimap.create())
.put(entry.getKey(), entry.getValue());
}
}
containerAnnotationData = mapBuilder;
}
return containerAnnotationData.get(container);
}
}

0 comments on commit bf4151d

Please sign in to comment.