Skip to content

Commit

Permalink
Better mixin infrastructure (#168)
Browse files Browse the repository at this point in the history
* Better mixin infrastructure

* stiching -> stitching

* rework fastcraft compat
  • Loading branch information
JL2210 authored Aug 4, 2024
1 parent 0e16c68 commit 86d9a76
Show file tree
Hide file tree
Showing 16 changed files with 321 additions and 60 deletions.
23 changes: 23 additions & 0 deletions src/main/java/me/eigenraven/lwjgl3ify/Lwjgl3ifyLateMixins.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package me.eigenraven.lwjgl3ify;

import java.util.List;
import java.util.Set;

import com.gtnewhorizon.gtnhmixins.ILateMixinLoader;
import com.gtnewhorizon.gtnhmixins.LateMixin;

import me.eigenraven.lwjgl3ify.mixins.Mixins;

@LateMixin
public class Lwjgl3ifyLateMixins implements ILateMixinLoader {

@Override
public String getMixinConfig() {
return "mixins.lwjgl3ify.late.json";
}

@Override
public List<String> getMixins(Set<String> loadedMods) {
return Mixins.getLateMixins(loadedMods);
}
}
8 changes: 4 additions & 4 deletions src/main/java/me/eigenraven/lwjgl3ify/core/Config.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public class Config {
private static boolean configLoaded = false;

public static boolean MIXIN_STBI_TEXTURE_LOADING = true;
public static boolean MIXIN_STBI_TEXTURE_STICHING = true;
public static boolean MIXIN_STBI_TEXTURE_STITCHING = true;
public static boolean MIXIN_STBI_IGNORE_FASTCRAFT = false;

public static boolean DEBUG_PRINT_KEY_EVENTS = false;
Expand Down Expand Up @@ -84,10 +84,10 @@ public static void reloadConfigObject() {
CATEGORY_MIXIN,
MIXIN_STBI_TEXTURE_LOADING,
"Use the faster stb_image-based texture loader");
MIXIN_STBI_TEXTURE_STICHING = config.getBoolean(
"stbiTextureStiching",
MIXIN_STBI_TEXTURE_STITCHING = config.getBoolean(
"stbiTextureStitching",
CATEGORY_MIXIN,
MIXIN_STBI_TEXTURE_STICHING,
MIXIN_STBI_TEXTURE_STITCHING,
"Use the much faster stb_rectpack-based texture stitcher");
MIXIN_STBI_IGNORE_FASTCRAFT = config.getBoolean(
"stbiIgnoreFastcraft",
Expand Down
51 changes: 5 additions & 46 deletions src/main/java/me/eigenraven/lwjgl3ify/core/Lwjgl3ifyCoremod.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package me.eigenraven.lwjgl3ify.core;

import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
Expand All @@ -15,14 +14,15 @@

import com.gtnewhorizon.gtnhmixins.IEarlyMixinLoader;

import cpw.mods.fml.relauncher.FMLLaunchHandler;
import cpw.mods.fml.relauncher.IFMLLoadingPlugin;
import me.eigenraven.lwjgl3ify.mixins.Mixins;

@IFMLLoadingPlugin.MCVersion("1.7.10")
@IFMLLoadingPlugin.SortingIndex(Integer.MAX_VALUE - 2)
public class Lwjgl3ifyCoremod implements IFMLLoadingPlugin, IEarlyMixinLoader {

public static final Logger LOGGER = LogManager.getLogger("lwjgl3ify");
public static Set<String> loadedCoreMods = null; // see Mixins.java

public Lwjgl3ifyCoremod() {
try {
Expand Down Expand Up @@ -72,52 +72,11 @@ public String getMixinConfig() {

@Override
public List<String> getMixins(Set<String> loadedCoreMods) {

final boolean hasFastcraft = loadedCoreMods.contains("fastcraft.Tweaker");
final boolean hasOptifine = loadedCoreMods.contains("optifine.OptiFineForgeTweaker");
List<String> mixins = new ArrayList<>(8);
// FML Java 9+ compatibility patches
mixins.add("fml.ItemStackHolderRef");
mixins.add("fml.JarDiscoverer");
mixins.add("fml.ObjectHolderRef");
mixins.add("fml.ObjectHolderRegistry");
if (FMLLaunchHandler.side()
.isClient()) {
// Improved KeyBinding handling to handle dead keys
mixins.add("game.MixinMinecraftKeyBinding");

// Adds the borderless mode
mixins.add("game.MixinBorderlessWindow");

// STB replacements for vanilla functions
if (Config.MIXIN_STBI_TEXTURE_LOADING) {
LOGGER.info("Enabling STB texture loading mixin");
mixins.add("game.MixinTextureAtlasSprite");
mixins.add("game.MixinTextureMap");
} else {
LOGGER.info("Disabling STB texture loading mixin");
}

final boolean fcBugFixedByOF = isFastcraftVersion1_25();
final boolean fcBugTriggered = hasFastcraft && !(hasOptifine && fcBugFixedByOF);
if (fcBugTriggered && !Config.MIXIN_STBI_IGNORE_FASTCRAFT) {
LOGGER.error(
"Not using STB stiching mixins because FastCraft is installed to prevent rapidly flashing screen. Remove FastCraft or "
+ (!fcBugFixedByOF ? "update to FastCraft 1.25 and " : "")
+ "add OptiFine to enable these performance-improving patches.");
} else {
if (Config.MIXIN_STBI_TEXTURE_STICHING) {
LOGGER.info("Enabling STB texture stitching mixin");
mixins.add("game.MixinStitcher");
} else {
LOGGER.info("Disabling STB texture stitching mixin");
}
}
}
return mixins;
this.loadedCoreMods = loadedCoreMods; // see Mixins.java
return Mixins.getEarlyMixins(loadedCoreMods);
}

private static boolean isFastcraftVersion1_25() {
public static boolean isFastcraftVersion1_25() {
// FastCraft tweaker hasn't run yet so no easy way to grab version.
// Let's compare the hash of fastcraft.a, which contains the version string in both 1.23 and 1.25.
try {
Expand Down
239 changes: 239 additions & 0 deletions src/main/java/me/eigenraven/lwjgl3ify/mixins/Mixins.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
package me.eigenraven.lwjgl3ify.mixins;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.function.Supplier;

import cpw.mods.fml.relauncher.FMLLaunchHandler;
import me.eigenraven.lwjgl3ify.core.Config;
import me.eigenraven.lwjgl3ify.core.Lwjgl3ifyCoremod;

// TODO: have someone who knows what they're doing rewrite these descriptions
public enum Mixins {

// client and server FML Java 9+ compatibility patches
FORGE_JAVA9(new Builder("FML Java 9+ compatibility patch").addTargetedMod(TargetedMod.VANILLA)
.setSide(Side.BOTH)
.setPhase(Phase.EARLY)
.addMixinClasses(
"fml.ItemStackHolderRef",
"fml.JarDiscoverer",
"fml.ObjectHolderRef",
"fml.ObjectHolderRegistry")
.setApplyIf(() -> true)),

// client only
FIX_DEADKEY_KEYBINDING(
new Builder("Improved KeyBinding handling to handle dead keys").addTargetedMod(TargetedMod.VANILLA)
.setSide(Side.CLIENT)
.setPhase(Phase.EARLY)
.addMixinClasses("game.MixinMinecraftKeyBinding")
.setApplyIf(() -> true)),
BORDERLESS_FULLSCREEN(new Builder("Adds the borderless mode").addTargetedMod(TargetedMod.VANILLA)
.setSide(Side.CLIENT)
.setPhase(Phase.EARLY)
.addMixinClasses("game.MixinBorderlessWindow")
.setApplyIf(() -> true)),
STB_LOADING(new Builder("STB texture loading mixin").addTargetedMod(TargetedMod.VANILLA)
.setSide(Side.CLIENT)
.setPhase(Phase.EARLY)
.addMixinClasses("game.MixinTextureAtlasSprite", "game.MixinTextureMap")
.setApplyIf(() -> Config.MIXIN_STBI_TEXTURE_LOADING)),
STB_STITCHING(new Builder("STB texture stitching mixin").addTargetedMod(TargetedMod.VANILLA)
.setSide(Side.CLIENT)
.setPhase(Phase.EARLY)
.addMixinClasses("game.MixinStitcher")
.setApplyIf(() -> shouldApplyMixinStitcher()));

private static boolean shouldApplyMixinStitcher() {
// this is bad and I hate it, but:
// 1: it fits well with upstream code from Hodgepodge,
// 2: it allows logging the message about updating FastCraft and installing Optifine, and
// 3: it neatly handles the complex logic that is required to avoid the bug
Set<String> loadedCoreMods = Lwjgl3ifyCoremod.loadedCoreMods;
boolean fcVer1_25 = Lwjgl3ifyCoremod.isFastcraftVersion1_25();
boolean noFastcraft = !loadedCoreMods.contains(TargetedMod.FASTCRAFT.coreModClass);
boolean hasOptifine = loadedCoreMods.contains(TargetedMod.OPTIFINE.coreModClass);
boolean shouldApply = noFastcraft || (hasOptifine && fcVer1_25) || Config.MIXIN_STBI_IGNORE_FASTCRAFT;
if (!shouldApply) {
Lwjgl3ifyCoremod.LOGGER.error(
"Not using STB stitching mixins because FastCraft is installed to prevent rapidly flashing screen. Remove FastCraft or "
+ (!fcVer1_25 ? "update to FastCraft 1.25 and " : "")
+ "add OptiFine to enable these performance-improving patches.");
return false;
}
return Config.MIXIN_STBI_TEXTURE_STITCHING;
}

private final List<String> mixinClasses;
private final List<TargetedMod> targetedMods;
private final List<TargetedMod> excludedMods;
private final Supplier<Boolean> applyIf;
private final Phase phase;
private final Side side;

Mixins(Builder builder) {
this.mixinClasses = builder.mixinClasses;
this.targetedMods = builder.targetedMods;
this.excludedMods = builder.excludedMods;
this.applyIf = builder.applyIf;
this.phase = builder.phase;
this.side = builder.side;
if (this.mixinClasses.isEmpty()) {
throw new RuntimeException("No mixin class specified for Mixin : " + this.name());
}
if (this.targetedMods.isEmpty()) {
throw new RuntimeException("No targeted mods specified for Mixin : " + this.name());
}
if (this.applyIf == null) {
throw new RuntimeException("No ApplyIf function specified for Mixin : " + this.name());
}
if (this.phase == null) {
throw new RuntimeException("No Phase specified for Mixin : " + this.name());
}
if (this.side == null) {
throw new RuntimeException("No Side function specified for Mixin : " + this.name());
}
}

public static List<String> getEarlyMixins(Set<String> loadedCoreMods) {
final List<String> mixins = new ArrayList<>();
final List<String> notLoading = new ArrayList<>();
for (Mixins mixin : Mixins.values()) {
if (mixin.phase == Phase.EARLY) {
if (mixin.shouldLoad(loadedCoreMods, Collections.emptySet())) {
mixins.addAll(mixin.mixinClasses);
} else {
notLoading.addAll(mixin.mixinClasses);
}
}
}
Lwjgl3ifyCoremod.LOGGER.info("Not loading the following EARLY mixins: {}", notLoading.toString());
return mixins;
}

public static List<String> getLateMixins(Set<String> loadedMods) {
// NOTE: Any targetmod here needs a modid, not a coremod id
final List<String> mixins = new ArrayList<>();
final List<String> notLoading = new ArrayList<>();
for (Mixins mixin : Mixins.values()) {
if (mixin.phase == Phase.LATE) {
if (mixin.shouldLoad(Collections.emptySet(), loadedMods)) {
mixins.addAll(mixin.mixinClasses);
} else {
notLoading.addAll(mixin.mixinClasses);
}
}
}
Lwjgl3ifyCoremod.LOGGER.info("Not loading the following LATE mixins: {}", notLoading.toString());
return mixins;
}

private boolean shouldLoadSide() {
return side == Side.BOTH || (side == Side.SERVER && FMLLaunchHandler.side()
.isServer())
|| (side == Side.CLIENT && FMLLaunchHandler.side()
.isClient());
}

private boolean allModsLoaded(List<TargetedMod> targetedMods, Set<String> loadedCoreMods, Set<String> loadedMods) {
if (targetedMods.isEmpty()) return false;

for (TargetedMod target : targetedMods) {
if (target == TargetedMod.VANILLA) continue;

// Check coremod first
if (!loadedCoreMods.isEmpty() && target.coreModClass != null
&& !loadedCoreMods.contains(target.coreModClass)) return false;
else if (!loadedMods.isEmpty() && target.modId != null && !loadedMods.contains(target.modId)) return false;
}

return true;
}

private boolean noModsLoaded(List<TargetedMod> targetedMods, Set<String> loadedCoreMods, Set<String> loadedMods) {
if (targetedMods.isEmpty()) return true;

for (TargetedMod target : targetedMods) {
if (target == TargetedMod.VANILLA) continue;

// Check coremod first
if (!loadedCoreMods.isEmpty() && target.coreModClass != null
&& loadedCoreMods.contains(target.coreModClass)) return false;
else if (!loadedMods.isEmpty() && target.modId != null && loadedMods.contains(target.modId)) return false;
}

return true;
}

private boolean shouldLoad(Set<String> loadedCoreMods, Set<String> loadedMods) {
return (shouldLoadSide() && applyIf.get()
&& allModsLoaded(targetedMods, loadedCoreMods, loadedMods)
&& noModsLoaded(excludedMods, loadedCoreMods, loadedMods));
}

private static class Builder {

private final String name;
private final List<String> mixinClasses = new ArrayList<>();
private final List<TargetedMod> targetedMods = new ArrayList<>();
private final List<TargetedMod> excludedMods = new ArrayList<>();
private Supplier<Boolean> applyIf = null;
private Phase phase = null;
private Side side = null;

public Builder(String name) {
this.name = name;
}

public Builder addMixinClasses(String... mixinClasses) {
this.mixinClasses.addAll(Arrays.asList(mixinClasses));
return this;
}

public Builder setPhase(Phase phase) {
if (this.phase != null) {
throw new RuntimeException("Trying to define Phase twice for " + this.name);
}
this.phase = phase;
return this;
}

public Builder setSide(Side side) {
if (this.side != null) {
throw new RuntimeException("Trying to define Side twice for " + this.name);
}
this.side = side;
return this;
}

public Builder setApplyIf(Supplier<Boolean> applyIf) {
this.applyIf = applyIf;
return this;
}

public Builder addTargetedMod(TargetedMod mod) {
this.targetedMods.add(mod);
return this;
}

public Builder addExcludedMod(TargetedMod mod) {
this.excludedMods.add(mod);
return this;
}
}

private enum Side {
BOTH,
CLIENT,
SERVER
}

private enum Phase {
EARLY,
LATE,
}
}
Loading

0 comments on commit 86d9a76

Please sign in to comment.