Skip to content

Commit

Permalink
Vanilla Crash & Furnace Speedup (#37)
Browse files Browse the repository at this point in the history
* Vanilla Crash & Furnace Speedup
1) Backports a fix for URL parsing from MinecraftForge/MinecraftForge#1712
2) Makes FurnaceRecipe significantly faster by using a HashMap w/ ItemStackHashingStrategy

* Thermos sledgehammer ASM for the furnace fix

* Overwrite instead of ignore.
 * Going with the assumption GT registers things later and we want those.  At first glance, most of the conflicts were dust -> ic2Ingot
Update hashing strategy slightly

* asm tweaks
  • Loading branch information
mitchej123 authored Dec 11, 2021
1 parent eafe6b9 commit 43e67ca
Show file tree
Hide file tree
Showing 10 changed files with 271 additions and 13 deletions.
1 change: 1 addition & 0 deletions dependencies.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,5 @@ dependencies {
compileOnly("curse.maven:cofh-core-69162:2388751") {
transitive = false
}
compileOnly files("dependencies/Thermos-1.7.10-1614-stripped.jar")
}
Binary file added dependencies/Thermos-1.7.10-1614-stripped.jar
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,11 @@ public enum AsmTransformers {
),
FIX_POTION_EFFECT_RENDERING("Move vanilla potion effect status rendering before everything else",
() -> HodgepodgeMixinPlugin.config.fixPotionEffectRender,
Collections.singletonList("com.mitchej123.hodgepodge.asm.InventoryEffectRendererTransformer"))
Collections.singletonList("com.mitchej123.hodgepodge.asm.InventoryEffectRendererTransformer")),
THERMOS_SLEDGEHAMMER_FURNACE_FIX("Take a sledgehammer to CraftServer.resetRecipes() to prevent it from breaking our Furnace Fix",
()-> HodgepodgeMixinPlugin.thermosTainted && HodgepodgeMixinPlugin.config.speedupVanillaFurnace,
Collections.singletonList("com.mitchej123.hodgepodge.asm.ThermosFurnaceSledgeHammer")
)
;

private final String name;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,9 @@ public byte[] transform(String name, String transformedName, byte[] basicClass)
}
cn.accept(cw);
return cw.toByteArray();
} else {
}
else {
return basicClass;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package com.mitchej123.hodgepodge.asm;

import com.mitchej123.hodgepodge.core.HodgepodgeMixinPlugin;
import net.minecraft.launchwrapper.IClassTransformer;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.InsnList;
import org.objectweb.asm.tree.InsnNode;
import org.objectweb.asm.tree.MethodNode;

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

@SuppressWarnings("unused")
public class ThermosFurnaceSledgeHammer implements IClassTransformer {
private static final Logger LOGGER = LogManager.getLogger("ThermosFurnaceSledgeHammer");
@Override
public byte[] transform(String name, String transformedName, byte[] basicClass) {
if (HodgepodgeMixinPlugin.config.thermosCraftServerClass.equals(transformedName)) {
LOGGER.info("Patching Thermos or derivative to not break our furnace fix");
final ClassReader cr = new ClassReader(basicClass);
final ClassWriter cw = new ClassWriter(0);

final ClassNode cn = new ClassNode(ASM5);
cr.accept(cn, 0);
for (MethodNode m : cn.methods) {
if ("resetRecipes".equals(m.name)) {
LOGGER.info("Taking a sledgehammer to CraftServer.resetRecipes()");
//Replace the body with a RETURN opcode
InsnList insnList = new InsnList();
insnList.add(new InsnNode(Opcodes.RETURN));
m.instructions = insnList;
m.maxStack = 0;
}
}
cn.accept(cw);
return cw.toByteArray();
}
else {
return basicClass;
}
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public class HodgepodgeMixinPlugin implements IMixinConfigPlugin {
Class.forName("org.bukkit.World");
thermosTainted = true;
log.warn("Thermos/Bukkit detected; This is an unsupported configuration -- Things may not function properly.");
log.warn(" Using `{}` for CraftServer Package. If this is not correct, please update your config file!", config.thermosCraftServerClass);
} catch (ClassNotFoundException e) {
thermosTainted = false;
log.info("Thermos/Bukkit NOT detected :-D");
Expand Down Expand Up @@ -176,17 +177,23 @@ public enum MixinSets {
() -> config.fixHopperHitBox,
Collections.singletonList("fixHopperHitBox.MixinBlockHopper")),
FIX_GET_BLOCK_LIGHT_VALUE("Fix vanilla light value calculation NPE",
() -> config.fixGetBlockLightValue,
Collections.singletonList("fixGetBlockLightValue.MixinWorld")),
() -> config.fixGetBlockLightValue,
Collections.singletonList("fixGetBlockLightValue.MixinWorld")),
FIX_FIRE_SPREAD("Fix vanilla fire spread NPE",
() -> config.fixFireSpread,
Collections.singletonList("fixFireSpread.MixinBlockFire")),
() -> config.fixFireSpread,
Collections.singletonList("fixFireSpread.MixinBlockFire")),
TILE_RENDERER_PROFILER("Shows renderer's impact on FPS in vanilla lagometer",
() -> config.enableTileRendererProfiler,
Arrays.asList("profiler.TileEntityRendererDispatcherMixin", "profiler.MinecraftMixin")),
() -> config.enableTileRendererProfiler,
Arrays.asList("profiler.TileEntityRendererDispatcherMixin", "profiler.MinecraftMixin")),
ADD_CV_SUPPORT_TO_WAND_PEDESTAL("Add CV support to Thaumcraft wand recharge pedestal",
()->config.addCVSupportToWandPedestal,
Collections.singletonList("wandPedestalCV.MixinTileWandPedestal"))
()->config.addCVSupportToWandPedestal,
Collections.singletonList("wandPedestalCV.MixinTileWandPedestal")),
FIX_FORGE_URL_DETECTION("Fix URISyntaxException in Forge Chat",
() -> config.fixUrlDetection,
Collections.singletonList("fixUrlDetection.MixinForgeHooks")),
SPEEDUP_VANILLA_FURNACE("Speedup vanilla furnace recipe lookup",
() -> config.speedupVanillaFurnace,
Collections.singletonList("speedupVanillaFurnace.MixinFurnaceRecipes"))
;


Expand Down
13 changes: 11 additions & 2 deletions src/main/java/com/mitchej123/hodgepodge/core/LoadingConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,15 @@ public class LoadingConfig {
public boolean fixPotionEffectRender;
public boolean addCVSupportToWandPedestal;
public boolean speedupProgressBar;
public boolean fixUrlDetection;
public boolean speedupVanillaFurnace;
// ASM
public boolean pollutionAsm;
public boolean cofhWorldTransformer;
public boolean enableTileRendererProfiler;

public String thermosCraftServerClass;


public static Configuration config;

Expand All @@ -63,6 +68,7 @@ public LoadingConfig(File file) {
fixHopperHitBox = config.get("fixes", "fixHopperHitBox", true, "Fix vanilla hopper hit box").getBoolean();
fixGetBlockLightValue = config.get("fixes", "fixGetBlockLightValue", true, "Fix vanilla light calculation sometimes cause NPE on thermos").getBoolean();
fixFireSpread = config.get("fixes", "fixFireSpread", true, "Fix vanilla fire spread sometimes cause NPE on thermos").getBoolean();
fixUrlDetection = config.get("fixes", "fixUrlDetection", true, "Fix URISyntaxException in forge.").getBoolean();

fixPotionEffectRender = config.get("tweaks", "fixPotionEffectRender", true, "Move vanilla potion effect status rendering before everything else").getBoolean();
installAnchorAlarm = config.get("tweaks", "installAnchorAlarm", true, "Wake up passive & personal anchors on player login").getBoolean();
Expand All @@ -75,19 +81,22 @@ public LoadingConfig(File file) {
ic2SeedMaxStackSize = config.get("tweaks", "ic2SeedMaxStackSize", 64, "IC2 seed max stack size").getInt();

speedupChunkCoordinatesHashCode = config.get("speedups", "speedupChunkCoordinatesHashCode", true, "Speedup ChunkCoordinates hashCode").getBoolean();
speedupVanillaFurnace = config.get("speedups", "speedupVanillaFurnace", true, "Speedup Vanilla Furnace recipe lookup").getBoolean();

speedupProgressBar = config.get("asm", "speedupProgressBar", true, "Speedup progressbar").getBoolean();
pollutionAsm = config.get("asm", "pollutionAsm", true, "Enable pollution rendering ASM").getBoolean();
cofhWorldTransformer = config.get("asm", "cofhWorldTransformer", true, "Enable Glease's ASM patch to disable unused CoFH tileentity cache").getBoolean();

thermosCraftServerClass = config.get("asm", "thermosCraftServerClass", "org.bukkit.craftbukkit.v1_7_R4.CraftServer", "If using Bukkit/Thermos, the CraftServer package.").getString();

if (config.hasChanged())
config.save();
}

public static void postInitClient() {
//need to be done later cause it initializes classes
// need to be done later cause it initializes classes
if (config == null) {
System.err.println("Didnt load HODGE");
System.err.println("Didn't load HODGEPODGE");
config = new Configuration(new File(Launch.minecraftHome, "config/hodgepodge.cfg"));
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.mitchej123.hodgepodge.core.util;

import gnu.trove.strategy.HashingStrategy;
import net.minecraft.item.ItemStack;

/*
* Strategy to make ItemStacks Hashable
* - Taken from https://github.com/hilburn/AdvancedSystemsManager/blob/master/src/main/java/advancedsystemsmanager/flow/execution/buffers/maps/ItemStackHashingStrategy.java
* under the the DBaJ (Don't Be a Jerk) non-commercial care-free license.
* (c) hilburn
*/
public class ItemStackHashingStrategy implements HashingStrategy<ItemStack> {
public static final ItemStackHashingStrategy INSTANCE = new ItemStackHashingStrategy();

@Override
public int computeHashCode(ItemStack stack) {
return stack.getItem().hashCode() ^ (stack.getItemDamage() * 31) ^ (stack.hasTagCompound() ? stack.stackTagCompound.hashCode() : 0);
}

@Override
public boolean equals(ItemStack stack1, ItemStack stack2) {
return stack1 != null && stack1.isItemEqual(stack2) && ItemStack.areItemStackTagsEqual(stack1, stack2);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package com.mitchej123.hodgepodge.mixins.fixUrlDetection;

import net.minecraft.event.ClickEvent;
import net.minecraft.util.ChatComponentText;
import net.minecraft.util.IChatComponent;
import net.minecraftforge.common.ForgeHooks;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Overwrite;

import java.net.URI;
import java.net.URISyntaxException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@Mixin(ForgeHooks.class)
public class MixinForgeHooks {
private static final Pattern URL_PATTERN = Pattern.compile(
// schema ipv4 OR namespace port path ends
// |-----------------| |-------------------------| |-------------------------| |---------| |--| |---------------|
"((?:[a-z0-9]{2,}:\\/\\/)?(?:(?:[0-9]{1,3}\\.){3}[0-9]{1,3}|(?:[-\\w_]{1,}\\.[a-z]{2,}?))(?::[0-9]{1,5})?.*?(?=[!\"\u00A7 \n]|$))",
Pattern.CASE_INSENSITIVE);

/**
* @author LexManos
* Backported from https://github.com/MinecraftForge/MinecraftForge/commit/5b28eb53e8623448b1c2bdb46b8924662e690995
*/
@Overwrite
public static IChatComponent newChatWithLinks(String string) {
// Includes ipv4 and domain pattern
// Matches an ip (xx.xxx.xx.xxx) or a domain (something.com) with or
// without a protocol or path.
IChatComponent ichat = new ChatComponentText("");
Matcher matcher = MixinForgeHooks.URL_PATTERN.matcher(string);
int lastEnd = 0;

// Find all urls
while (matcher.find()) {
int start = matcher.start();
int end = matcher.end();

// Append the previous left overs.
ichat.appendText(string.substring(lastEnd, start));
lastEnd = end;
String url = string.substring(start, end);
IChatComponent link = new ChatComponentText(url);

try {
// Add schema so client doesn't crash.
if ((new URI(url)).getScheme() == null)
url = "http://" + url;
}
catch (URISyntaxException e) {
// Bad syntax bail out!
ichat.appendText(url);
continue;
}

// Set the click event and append the link.
ClickEvent click = new ClickEvent(ClickEvent.Action.OPEN_URL, url);
link.getChatStyle().setChatClickEvent(click);
ichat.appendSibling(link);
}

// Append the rest of the message.
ichat.appendText(string.substring(lastEnd));
return ichat;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package com.mitchej123.hodgepodge.mixins.speedupVanillaFurnace;

import com.mitchej123.hodgepodge.core.HodgepodgeMixinPlugin;
import com.mitchej123.hodgepodge.core.util.ItemStackHashingStrategy;
import gnu.trove.map.hash.TCustomHashMap;
import net.minecraft.block.Block;
import net.minecraft.item.ItemStack;
import net.minecraft.item.crafting.FurnaceRecipes;
import net.minecraft.launchwrapper.Launch;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Overwrite;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;

import java.lang.reflect.Field;
import java.util.Map;

@Mixin(FurnaceRecipes.class)
public abstract class MixinFurnaceRecipes {
/*
* Speed up FurnaceRecipes.getSmeltingResult by:
* 1) Hijacking the constructor here to recreate the lists with a replacement hash map and an ItemStack hashing strategy
* 2) No longer looping over every. single. recipe. in the list and using the .get()
*/
@Shadow private Map smeltingList;
@Shadow private Map experienceList;
@Shadow abstract boolean func_151397_a(ItemStack p_151397_1_, ItemStack p_151397_2_);

@Redirect(
at=@At(
value="INVOKE",
target="Lnet/minecraft/item/crafting/FurnaceRecipes;func_151393_a(Lnet/minecraft/block/Block;Lnet/minecraft/item/ItemStack;F)V",
ordinal = 0
),
method="Lnet/minecraft/item/crafting/FurnaceRecipes;<init>()V"
)
private void doStuff(FurnaceRecipes instance, Block p_151393_1_, ItemStack p_151393_2_, float p_151393_3_) throws NoSuchFieldException, IllegalAccessException {
HodgepodgeMixinPlugin.log.info("Swapping out smeltingList and experienceList in FurnaceRecipes");

// Hack into the first call in the constructor and replace the lists with a new hashmap that has an ItemStackMi hashing strategy
boolean devEnv = (Boolean) Launch.blackboard.get("fml.deobfuscatedEnvironment");
try {
Class<?> clazz = Class.forName("net.minecraft.item.crafting.FurnaceRecipes");

Field smeltingList = clazz.getDeclaredField(devEnv ? "smeltingList" : "field_77604_b");
smeltingList.setAccessible(true);
smeltingList.set(instance, new TCustomHashMap<ItemStack, ItemStack>(ItemStackHashingStrategy.INSTANCE));

Field experienceList = clazz.getDeclaredField(devEnv ? "experienceList" : "field_77605_c");
experienceList.setAccessible(true);
experienceList.set(instance, new TCustomHashMap<ItemStack, Float>(ItemStackHashingStrategy.INSTANCE));

HodgepodgeMixinPlugin.log.info("Successfully swapped the lists in FurnaceRecipes");

} catch (ClassNotFoundException | IllegalAccessException e) {
e.printStackTrace();
}
instance.func_151393_a(p_151393_1_, p_151393_2_, p_151393_3_);
}
/**
* @author mitchej123
* Inspired by later versions of forge
*/
@SuppressWarnings("unchecked")
@Overwrite(remap = false)
public void func_151394_a /* addSmeltingRecipe */ (ItemStack input, ItemStack stack, float experience) {
if (getSmeltingResult(input) != null) {
HodgepodgeMixinPlugin.log.info("Overwriting smelting recipe for input: {} and output {} with {}", input, getSmeltingResult(input), stack);
}
this.smeltingList.put(input, stack);
this.experienceList.put(stack, experience);
}

/**
* @author mitchej123
* Significantly Faster
*/
@Overwrite
public ItemStack getSmeltingResult(ItemStack stack) {
return (ItemStack) this.smeltingList.get(stack);
}

/**
* @author mitchej123
* Significantly Faster
*/
@Overwrite(remap = false)
public float func_151398_b /* getSmeltingExperience */ (ItemStack stack) {
float exp = stack.getItem().getSmeltingExperience(stack);
if (exp == -1) {
exp = (Float) (this.experienceList.get(stack));
}
return exp;
}

}

0 comments on commit 43e67ca

Please sign in to comment.