Skip to content

Commit

Permalink
bug fix data cache to fix compat with mixin squared
Browse files Browse the repository at this point in the history
  • Loading branch information
isXander committed Jun 14, 2024
1 parent 518c2ae commit 68fa311
Show file tree
Hide file tree
Showing 11 changed files with 176 additions and 65 deletions.
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package dev.isxander.debugify.test;

import dev.isxander.debugify.test.suites.MC8187;
import net.minecraft.core.BlockPos;
import net.minecraft.gametest.framework.GameTestHelper;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.level.GameType;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;

Expand All @@ -16,7 +18,7 @@
* Utilities for tests.
*
* @author Ampflower
* @see DebugifyMc8187TestSuite
* @see MC8187
* @since 1.20.1+1.2
**/
public final class DebugifyTestUtils {
Expand All @@ -25,32 +27,32 @@ public final class DebugifyTestUtils {
/**
* Utility function to force-feed saplings bonemeal.
*/
static void bonemealIndefinitely(GameTestHelper ctx, BlockPos pos) {
final var player = ctx.makeMockPlayer();
public static void bonemealIndefinitely(GameTestHelper ctx, BlockPos pos) {
final var player = ctx.makeMockPlayer(GameType.SURVIVAL);

player.setItemSlot(EquipmentSlot.MAINHAND, new ItemStack(Items.BONE_MEAL, Item.MAX_STACK_SIZE));
player.setItemSlot(EquipmentSlot.MAINHAND, new ItemStack(Items.BONE_MEAL, Item.DEFAULT_MAX_STACK_SIZE));

ctx.onEachTick(() -> ctx.useBlock(pos, player));
}

/**
* Asserts that blocks in a given area are as expected.
*/
static void assertBlockFill(GameTestHelper ctx, BlockPos start, BlockPos end, Predicate<Block> test, String message) {
public static void assertBlockFill(GameTestHelper ctx, BlockPos start, BlockPos end, Predicate<Block> test, String message) {
area(start, end, pos -> ctx.assertBlock(pos, test, message));
}

/**
* Asserts that blockstates in a given area are as expected.
*/
static void assertBlockStateFill(GameTestHelper ctx, BlockPos start, BlockPos end, Predicate<BlockState> test, String message) {
public static void assertBlockStateFill(GameTestHelper ctx, BlockPos start, BlockPos end, Predicate<BlockState> test, String message) {
area(start, end, pos -> ctx.assertBlockState(pos, test, () -> message));
}

/**
* Fills the area with the given blockstate.
*/
static void fill(GameTestHelper ctx, BlockPos start, BlockPos end, BlockState state) {
public static void fill(GameTestHelper ctx, BlockPos start, BlockPos end, BlockState state) {
area(start, end, pos -> ctx.setBlock(pos, state));
}

Expand Down
42 changes: 42 additions & 0 deletions src/gametest/java/dev/isxander/debugify/test/suites/MC100991.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package dev.isxander.debugify.test.suites;

import net.fabricmc.fabric.api.gametest.v1.FabricGameTest;
import net.minecraft.core.BlockPos;
import net.minecraft.gametest.framework.GameTest;
import net.minecraft.gametest.framework.GameTestHelper;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.Mob;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.entity.projectile.FishingHook;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.level.GameType;

public class MC100991 implements FabricGameTest {
@GameTest(template = EMPTY_STRUCTURE)
public void statTrackRodKill(GameTestHelper ctx) {
Mob targetMob = ctx.spawnWithNoFreeWill(EntityType.CREEPER, new BlockPos(4, 0, 4));

Mob attacker = ctx.spawnWithNoFreeWill(EntityType.HUSK, new BlockPos(1, 0, 1));
ItemStack rodStack = new ItemStack(Items.FISHING_ROD);
attacker.setItemInHand(InteractionHand.MAIN_HAND, rodStack);

FishingHook hook = ctx.spawn(EntityType.FISHING_BOBBER, targetMob.blockPosition().above(1));

Player mockPlayer = ctx.makeMockPlayer(GameType.CREATIVE);
mockPlayer.setItemInHand(InteractionHand.MAIN_HAND, rodStack);
mockPlayer.setPos(attacker.position());
hook.setOwner(mockPlayer);

ctx.runAfterDelay(20, () -> {
// yank entity
hook.retrieve(rodStack);

ctx.runAfterDelay(5, () -> {
System.out.println(targetMob.getCombatTracker().getDeathMessage().getString());
System.out.println(targetMob.getCombatTracker().getDeathMessage());
});
});
}
}
34 changes: 34 additions & 0 deletions src/gametest/java/dev/isxander/debugify/test/suites/MC30391.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package dev.isxander.debugify.test.suites;

import net.fabricmc.fabric.api.gametest.v1.FabricGameTest;
import net.minecraft.client.Minecraft;
import net.minecraft.core.BlockPos;
import net.minecraft.gametest.framework.GameTest;
import net.minecraft.gametest.framework.GameTestHelper;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.Mob;

public class MC30391 implements FabricGameTest {

@GameTest(template = EMPTY_STRUCTURE)
public void blaze(GameTestHelper ctx) {
testWithEntity(ctx, EntityType.BLAZE);
}

@GameTest(template = EMPTY_STRUCTURE)
public void chicken(GameTestHelper ctx) {
testWithEntity(ctx, EntityType.CHICKEN);
}

@GameTest(template = EMPTY_STRUCTURE)
public void wither(GameTestHelper ctx) {
testWithEntity(ctx, EntityType.WITHER);
}

private <E extends Mob> void testWithEntity(GameTestHelper ctx, EntityType<E> entityType) {
Mob mob = ctx.spawnWithNoFreeWill(entityType, new BlockPos(0, 4, 0));

// TODO:
ctx.runAfterDelay(50, ctx::succeed);
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package dev.isxander.debugify.test;
package dev.isxander.debugify.test.suites;

import net.fabricmc.fabric.api.gametest.v1.FabricGameTest;
import net.minecraft.core.BlockPos;
Expand All @@ -9,7 +9,7 @@
import net.minecraft.world.entity.animal.Wolf;
import net.minecraft.world.entity.projectile.Snowball;

public class DebugifyMc72151TestSuite implements FabricGameTest {
public class MC72151 implements FabricGameTest {
@GameTest(template = EMPTY_STRUCTURE)
public void mc72151(GameTestHelper ctx) {
BlockPos wolfPos = new BlockPos(4, 0, 4);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package dev.isxander.debugify.test;
package dev.isxander.debugify.test.suites;

import dev.isxander.debugify.test.DebugifyTestUtils;
import net.fabricmc.fabric.api.gametest.v1.FabricGameTest;
import net.minecraft.core.BlockPos;
import net.minecraft.gametest.framework.GameTest;
Expand All @@ -16,7 +17,7 @@
* @author Ampflower
* @since 1.20.1+1.2
**/
public class DebugifyMc8187TestSuite implements FabricGameTest {
public class MC8187 implements FabricGameTest {
private static final String TEMPLATE = "debugify:mc-8187";

// Adjust this if you move the center, as the tests assume this is accurate.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package dev.isxander.debugify.test;
package dev.isxander.debugify.test.suites;

import net.fabricmc.fabric.api.gametest.v1.FabricGameTest;
import net.minecraft.gametest.framework.GameTest;
Expand All @@ -9,12 +9,13 @@
import net.minecraft.world.entity.animal.Wolf;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.Items;
import net.minecraft.world.level.GameType;

public class DebugifyMc93018TestSuite implements FabricGameTest {
public class MC93018 implements FabricGameTest {

@GameTest(template = EMPTY_STRUCTURE)
public void mc93018(GameTestHelper ctx) {
Player player = ctx.makeMockPlayer();
Player player = ctx.makeMockPlayer(GameType.SURVIVAL);
player.setItemInHand(InteractionHand.MAIN_HAND, Items.BONE.getDefaultInstance());
ctx.getLevel().addFreshEntity(player);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
DataVersion: 3463,
size: [18, 2, 18],
size: [18, 50, 18],
data: [
{pos: [8, 0, 8], state: "minecraft:grass_block{snowy:false}"},
{pos: [8, 0, 9], state: "minecraft:grass_block{snowy:false}"},
Expand Down
8 changes: 5 additions & 3 deletions src/gametest/resources/fabric.mod.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@
"dev.isxander.debugify.test.DebugifyGameTestMain"
],
"fabric-gametest": [
"dev.isxander.debugify.test.DebugifyMc72151TestSuite",
"dev.isxander.debugify.test.DebugifyMc8187TestSuite",
"dev.isxander.debugify.test.DebugifyMc93018TestSuite"
"dev.isxander.debugify.test.suites.MC72151",
"dev.isxander.debugify.test.suites.MC8187",
"dev.isxander.debugify.test.suites.MC93018",
"dev.isxander.debugify.test.suites.MC30391",
"dev.isxander.debugify.test.suites.MC100991"
],
"debugify": [
"dev.isxander.debugify.test.DebugifyApiTest"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package dev.isxander.debugify.mixinplugin;

import dev.isxander.debugify.fixes.BugFix;
import dev.isxander.debugify.fixes.BugFixData;
import dev.isxander.debugify.fixes.FixCategory;
import dev.isxander.debugify.fixes.OS;
import org.jetbrains.annotations.Nullable;
import org.objectweb.asm.tree.AnnotationNode;
import org.objectweb.asm.tree.ClassNode;
import org.spongepowered.asm.service.MixinService;
import org.spongepowered.asm.util.Annotations;

import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

public class BugFixDataCache {
private static final Map<String, ResolvedBugFixData> bugFixDataByMixinClass = new HashMap<>();

static Optional<BugFixData> getOrResolve(String mixinClassName) {
return Optional.ofNullable(
bugFixDataByMixinClass.computeIfAbsent(mixinClassName, BugFixDataCache::resolve)
.data()
);
}

static Optional<BugFixData> getIfResolved(String mixinClassName) {
return Optional.ofNullable(bugFixDataByMixinClass.get(mixinClassName))
.flatMap(resolved -> Optional.ofNullable(resolved.data()));
}

private static ResolvedBugFixData resolve(String mixinClassName) {
ClassNode classNode = getClassNode(mixinClassName);
if (classNode == null) {
return new ResolvedBugFixData(null);
}

AnnotationNode annotationNode = Annotations.getVisible(classNode, BugFix.class);

if (annotationNode == null) {
return new ResolvedBugFixData(null);
}

String id = Annotations.getValue(annotationNode, "id");
FixCategory category = getAnnotationEnumValue(annotationNode, "category", FixCategory.class);
BugFix.Env env = getAnnotationEnumValue(annotationNode, "env", BugFix.Env.class);
boolean enabledByDefault = Annotations.getValue(annotationNode, "enabled", Boolean.valueOf(true));
List<String> conflicts = Annotations.getValue(annotationNode, "modConflicts", true);
OS requiredOS = Annotations.getValue(annotationNode, "os", OS.class, OS.UNKNOWN);

return new ResolvedBugFixData(new BugFixData(id, category, env, enabledByDefault, conflicts, requiredOS));
}

private static ClassNode getClassNode(String className) {
try {
return MixinService.getService().getBytecodeProvider().getClassNode(className);
} catch (ClassNotFoundException | IOException e) {
return null;
}
}

private static <T extends Enum<T>> T getAnnotationEnumValue(AnnotationNode annotation, String key, Class<T> enumClass) {
String[] value = Annotations.getValue(annotation, key);
return Enum.valueOf(enumClass, value[1]);
}

record ResolvedBugFixData(@Nullable BugFixData data) {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,7 @@ public ErrorAction onApplyError(String targetClassName, Throwable th, IMixinInfo
}

private ErrorAction handleError(ErrorAction usualAction, IMixinInfo mixin) {
ClassNode classNode;
try {
classNode = MixinService.getService().getBytecodeProvider().getClassNode(mixin.getClassName(), false);
} catch (ClassNotFoundException | IOException e) {
return usualAction;
}

Optional<BugFixData> bugFix = MixinPlugin.getBugFixForMixin(classNode);
Optional<BugFixData> bugFix = BugFixDataCache.getIfResolved(mixin.getClassName());
if (bugFix.isEmpty())
return usualAction;

Expand Down
44 changes: 5 additions & 39 deletions src/main/java/dev/isxander/debugify/mixinplugin/MixinPlugin.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import dev.isxander.debugify.fixes.BugFixData;
import dev.isxander.debugify.fixes.OS;
import net.fabricmc.loader.api.FabricLoader;
import org.jetbrains.annotations.Nullable;
import org.objectweb.asm.tree.AnnotationNode;
import org.objectweb.asm.tree.ClassNode;
import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin;
Expand All @@ -15,11 +16,10 @@
import org.spongepowered.asm.util.Annotations;

import java.io.IOException;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.*;

public class MixinPlugin implements IMixinConfigPlugin {

@Override
public void onLoad(String mixinPackage) {
Debugify.onPreInitialize();
Expand Down Expand Up @@ -48,8 +48,7 @@ public String getRefMapperConfig() {

@Override
public boolean shouldApplyMixin(String targetClassName, String mixinClassName) {
ClassNode mixinClassNode = getClassNode(mixinClassName);
Optional<BugFixData> bugFixOptional = getBugFixForMixin(mixinClassNode);
Optional<BugFixData> bugFixOptional = BugFixDataCache.getOrResolve(mixinClassName);

if (bugFixOptional.isEmpty())
return true;
Expand All @@ -59,7 +58,7 @@ public boolean shouldApplyMixin(String targetClassName, String mixinClassName) {
Debugify.CONFIG.registerBugFix(bugFix);

if (DebugifyErrorHandler.hasErrored(bugFix)) {
Debugify.LOGGER.warn("Preventing loading of {} mixin, {} because another mixin for the same bug fix failed to apply.", bugFix.bugId(), mixinClassNode.name);
Debugify.LOGGER.warn("Preventing loading of {} mixin, {} because another mixin for the same bug fix failed to apply.", bugFix.bugId(), mixinClassName);
return false;
}

Expand All @@ -77,39 +76,6 @@ public boolean shouldApplyMixin(String targetClassName, String mixinClassName) {
return Debugify.CONFIG.isBugFixEnabled(bugFix);
}

static ClassNode getClassNode(String className) {
ClassNode classNode;

try {
classNode = MixinService.getService().getBytecodeProvider().getClassNode(className);
} catch (ClassNotFoundException | IOException e) {
classNode = null;
}

return classNode;
}

static Optional<BugFixData> getBugFixForMixin(ClassNode mixinClassNode) {
AnnotationNode annotationNode = Annotations.getVisible(mixinClassNode, BugFix.class);

if (annotationNode == null)
return Optional.empty();

String id = Annotations.getValue(annotationNode, "id");
FixCategory category = getAnnotationEnumValue(annotationNode, "category", FixCategory.class);
BugFix.Env env = getAnnotationEnumValue(annotationNode, "env", BugFix.Env.class);
boolean enabledByDefault = Annotations.getValue(annotationNode, "enabled", Boolean.valueOf(true));
List<String> conflicts = Annotations.getValue(annotationNode, "modConflicts", true);
OS requiredOS = Annotations.getValue(annotationNode, "os", OS.class, OS.UNKNOWN);

return Optional.of(new BugFixData(id, category, env, enabledByDefault, conflicts, requiredOS));
}

private static <T extends Enum<T>> T getAnnotationEnumValue(AnnotationNode annotation, String key, Class<T> enumClass) {
String[] value = Annotations.getValue(annotation, key);
return Enum.valueOf(enumClass, value[1]);
}

@Override
public void acceptTargets(Set<String> myTargets, Set<String> otherTargets) {}

Expand Down

0 comments on commit 68fa311

Please sign in to comment.