Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Better mixin infrastructure #168

Merged
merged 3 commits into from
Aug 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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