Skip to content
This repository was archived by the owner on Jun 3, 2024. It is now read-only.

Commit 2f18cbf

Browse files
authored
Merge pull request #47 from Nuclearfarts/master
Add support for IExtensibleEnum shenanigans
2 parents 80a0386 + 423b315 commit 2f18cbf

27 files changed

Lines changed: 1029 additions & 2 deletions

build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ allprojects {
6060

6161
implementation 'com.patchworkmc:patchwork-eventbus:0.1.2:all'
6262
implementation 'com.google.code.findbugs:jsr305:3.0.2'
63+
implementation 'org.apache.logging.log4j:log4j-core:2.10.0'
6364

6465
// For EventBus
6566
implementation 'org.ow2.asm:asm:6.2'

patchwork-enum-hacks/build.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
archivesBaseName = "patchwork-enum-hacks"
2+
version = getSubprojectVersion(project, "0.1.0")
Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
/*
2+
* Minecraft Forge, Patchwork Project
3+
* Copyright (c) 2016-2020, 2019-2020
4+
*
5+
* This library is free software; you can redistribute it and/or
6+
* modify it under the terms of the GNU Lesser General Public
7+
* License as published by the Free Software Foundation version 2.1
8+
* of the License.
9+
*
10+
* This library is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13+
* Lesser General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU Lesser General Public
16+
* License along with this library; if not, write to the Free Software
17+
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18+
*/
19+
20+
package com.patchworkmc.api.enumhacks;
21+
22+
import java.lang.invoke.CallSite;
23+
import java.lang.invoke.LambdaMetafactory;
24+
import java.lang.invoke.MethodHandle;
25+
import java.lang.invoke.MethodHandles;
26+
import java.lang.invoke.MethodType;
27+
import java.lang.reflect.Field;
28+
import java.util.function.IntFunction;
29+
import java.util.function.Predicate;
30+
31+
import com.google.common.collect.ImmutableList;
32+
import org.apache.commons.lang3.ArrayUtils;
33+
import net.minecraftforge.common.util.TriPredicate;
34+
35+
import net.minecraft.block.BlockState;
36+
import net.minecraft.block.entity.BannerPattern;
37+
import net.minecraft.enchantment.EnchantmentTarget;
38+
import net.minecraft.entity.EntityCategory;
39+
import net.minecraft.entity.EntityType;
40+
import net.minecraft.entity.SpawnRestriction;
41+
import net.minecraft.item.Item;
42+
import net.minecraft.item.ItemStack;
43+
import net.minecraft.structure.pool.StructurePool;
44+
import net.minecraft.structure.processor.StructureProcessor;
45+
import net.minecraft.util.Formatting;
46+
import net.minecraft.util.Rarity;
47+
import net.minecraft.util.math.BlockPos;
48+
import net.minecraft.world.ViewableWorld;
49+
import net.minecraft.world.gen.feature.OreFeatureConfig;
50+
51+
import com.patchworkmc.impl.enumhacks.HackableEnum;
52+
import com.patchworkmc.impl.enumhacks.PatchworkEnchantmentTarget;
53+
import com.patchworkmc.impl.enumhacks.PatchworkSpawnRestrictionLocation;
54+
import com.patchworkmc.mixin.enumhacks.BannerPatternAccessor;
55+
import com.patchworkmc.mixin.enumhacks.EntityCategoryAccessor;
56+
import com.patchworkmc.mixin.enumhacks.OreFeatureConfigTargetAccessor;
57+
import com.patchworkmc.mixin.enumhacks.RarityAccessor;
58+
import com.patchworkmc.mixin.enumhacks.SpawnRestrictionLocationAccessor;
59+
import com.patchworkmc.mixin.enumhacks.StructurePoolProjectionAccessor;
60+
61+
/**
62+
* A bunch of awful, awful hacks to implement IExtensibleEnum.
63+
* No, seriously. These are AWFUL hacks. Especially EnchantmentTarget.
64+
* @author NuclearFarts
65+
*/
66+
public final class EnumHacks {
67+
public EnumHacks() { }
68+
69+
private static final EnchantmentTargetFactory ENCHANTMENT_TARGET_FACTORY;
70+
private static final Field ENUM_CACHE;
71+
private static final Field ENUM_DIRECTORY_CACHE;
72+
73+
static {
74+
// Enum values are cached on Class objects. Store the Fields to reset the caches.
75+
boolean attemptDirectory = true;
76+
Field enumCache;
77+
78+
try {
79+
enumCache = Class.class.getDeclaredField("enumConstants");
80+
} catch (NoSuchFieldException e) {
81+
// don't blow up quite yet. we might be on openj9.
82+
try {
83+
enumCache = Class.class.getDeclaredField("enumVars");
84+
attemptDirectory = false; // if we didn't go into the catch block, we're on openj9, which caches both in one object. don't look for the other one.
85+
} catch (NoSuchFieldException e2) {
86+
// we aren't on openj9 either. blow up.
87+
throw new RuntimeException("Problem getting enumConstants field", e);
88+
}
89+
}
90+
91+
ENUM_CACHE = enumCache;
92+
ENUM_CACHE.setAccessible(true);
93+
94+
if (attemptDirectory) {
95+
try {
96+
ENUM_DIRECTORY_CACHE = Class.class.getDeclaredField("enumConstantDirectory");
97+
ENUM_DIRECTORY_CACHE.setAccessible(true);
98+
} catch (NoSuchFieldException | SecurityException e) {
99+
throw new RuntimeException("Problem getting enumConstantDirectory field", e);
100+
}
101+
} else {
102+
ENUM_DIRECTORY_CACHE = null;
103+
}
104+
105+
// We can't use a constructor accessor because we get around EnchantmentTarget being abstract by using EnchantmentTarget$1.
106+
// EnchantmentTarget$1 is a private anonymous internal class and cannot be used as a return type. Mixin doesn't like that so @Coerce won't work for some reason.
107+
// get a lookup that has access to EnchantmentTarget's private methods, including constructor.
108+
MethodHandles.Lookup lookup = ((PatchworkEnchantmentTarget) EnchantmentTarget.ALL).patchwork_getEnchantmentTargetPrivateLookup();
109+
MethodType type = MethodType.methodType(EnchantmentTarget.class, String.class, int.class);
110+
111+
try {
112+
MethodHandle enchTargetCtor = lookup.findConstructor(EnchantmentTarget.ALL.getClass(), type.changeReturnType(void.class)); // ctors have void return internally
113+
// LambdaMetafactory stuff is technically unnecessary but it means we don't have to catch Throwable every time we instantiate an EnchantmentTarget and I'd rather not do that.
114+
CallSite site = LambdaMetafactory.metafactory(lookup, "create", MethodType.methodType(EnchantmentTargetFactory.class), type, enchTargetCtor, type);
115+
ENCHANTMENT_TARGET_FACTORY = (EnchantmentTargetFactory) site.getTarget().invoke();
116+
} catch (Throwable e) {
117+
throw new RuntimeException("Could not get EnchantmentTarget constructor/set up factory", e);
118+
}
119+
}
120+
121+
@SuppressWarnings("unchecked")
122+
private static <T> void addToValues(T[] origArray, T newValue) {
123+
((HackableEnum<T>) newValue).patchwork_setValues(ArrayUtils.add(origArray, newValue));
124+
}
125+
126+
private static void clearCachedValues(Class<? extends Enum<?>> clazz) {
127+
try {
128+
ENUM_CACHE.set(clazz, null);
129+
130+
if (ENUM_DIRECTORY_CACHE != null) {
131+
ENUM_DIRECTORY_CACHE.set(clazz, null);
132+
}
133+
} catch (IllegalArgumentException | IllegalAccessException e) {
134+
throw new RuntimeException("Exception clearing enum cache for class " + clazz.getSimpleName(), e);
135+
}
136+
}
137+
138+
private static <T extends Enum<T>> T constructAndAdd(Class<T> clazz, IntFunction<? extends T> constructor) {
139+
T[] values = clazz.getEnumConstants();
140+
T instance = constructor.apply(values.length);
141+
addToValues(values, instance);
142+
clearCachedValues(clazz);
143+
return instance;
144+
}
145+
146+
public static Rarity createRarity(String name, Formatting formatting) {
147+
return constructAndAdd(Rarity.class, ordinal -> RarityAccessor.invokeConstructor(name, ordinal, formatting));
148+
}
149+
150+
public static EntityCategory createEntityCategory(String constantName, String name, int spawnCap, boolean peaceful, boolean animal) {
151+
return constructAndAdd(EntityCategory.class, ordinal -> EntityCategoryAccessor.invokeConstructor(constantName, ordinal, name, spawnCap, peaceful, animal));
152+
}
153+
154+
public static StructurePool.Projection createStructurePoolProjection(String name, String id, ImmutableList<StructureProcessor> processors) {
155+
StructurePool.Projection instance = constructAndAdd(StructurePool.Projection.class, ordinal -> StructurePoolProjectionAccessor.invokeConstructor(name, ordinal, id, processors));
156+
StructurePoolProjectionAccessor.getIdProjectionMap().put(id, instance);
157+
return instance;
158+
}
159+
160+
public static OreFeatureConfig.Target createOreFeatureConfigTarget(String constantName, String name, Predicate<BlockState> predicate) {
161+
OreFeatureConfig.Target instance = constructAndAdd(OreFeatureConfig.Target.class, ordinal -> OreFeatureConfigTargetAccessor.invokeConstructor(constantName, ordinal, name, predicate));
162+
OreFeatureConfigTargetAccessor.getNameMap().put(name, instance);
163+
return instance;
164+
}
165+
166+
public static BannerPattern createBannerPattern(String constantName, String name, String id, ItemStack baseStack) {
167+
return constructAndAdd(BannerPattern.class, ordinal -> BannerPatternAccessor.invokeConstructor(constantName, ordinal, name, id, baseStack));
168+
}
169+
170+
public static BannerPattern createBannerPattern(String constantName, String name, String id, String recipePattern0, String recipePattern1, String recipePattern2) {
171+
return constructAndAdd(BannerPattern.class, ordinal -> BannerPatternAccessor.invokeConstructor(constantName, ordinal, name, id, recipePattern0, recipePattern1, recipePattern2));
172+
}
173+
174+
public static SpawnRestriction.Location createSpawnRestrictionLocation(String name, TriPredicate<ViewableWorld, BlockPos, EntityType<?>> predicate) {
175+
SpawnRestriction.Location instance = constructAndAdd(SpawnRestriction.Location.class, ordinal -> SpawnRestrictionLocationAccessor.invokeConstructor(name, ordinal));
176+
((PatchworkSpawnRestrictionLocation) (Object) instance).patchwork_setPredicate(predicate);
177+
return instance;
178+
}
179+
180+
public static EnchantmentTarget createEnchantmentTarget(String name, Predicate<Item> predicate) {
181+
EnchantmentTarget instance = constructAndAdd(EnchantmentTarget.class, ordinal -> ENCHANTMENT_TARGET_FACTORY.create(name, ordinal));
182+
((PatchworkEnchantmentTarget) instance).patchwork_setPredicate(predicate);
183+
return instance;
184+
}
185+
186+
@FunctionalInterface
187+
public interface EnchantmentTargetFactory {
188+
EnchantmentTarget create(String target, int ordinal);
189+
}
190+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/*
2+
* Minecraft Forge, Patchwork Project
3+
* Copyright (c) 2016-2020, 2019-2020
4+
*
5+
* This library is free software; you can redistribute it and/or
6+
* modify it under the terms of the GNU Lesser General Public
7+
* License as published by the Free Software Foundation version 2.1
8+
* of the License.
9+
*
10+
* This library is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13+
* Lesser General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU Lesser General Public
16+
* License along with this library; if not, write to the Free Software
17+
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18+
*/
19+
20+
package com.patchworkmc.impl.enumhacks;
21+
22+
public interface HackableEnum<T> {
23+
void patchwork_setValues(T[] values);
24+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
* Minecraft Forge, Patchwork Project
3+
* Copyright (c) 2016-2020, 2019-2020
4+
*
5+
* This library is free software; you can redistribute it and/or
6+
* modify it under the terms of the GNU Lesser General Public
7+
* License as published by the Free Software Foundation version 2.1
8+
* of the License.
9+
*
10+
* This library is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13+
* Lesser General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU Lesser General Public
16+
* License along with this library; if not, write to the Free Software
17+
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18+
*/
19+
20+
package com.patchworkmc.impl.enumhacks;
21+
22+
import java.lang.invoke.MethodHandles;
23+
import java.util.function.Predicate;
24+
25+
import net.minecraft.item.Item;
26+
27+
public interface PatchworkEnchantmentTarget {
28+
void patchwork_setPredicate(Predicate<Item> predicate);
29+
30+
MethodHandles.Lookup patchwork_getEnchantmentTargetPrivateLookup();
31+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
* Minecraft Forge, Patchwork Project
3+
* Copyright (c) 2016-2020, 2019-2020
4+
*
5+
* This library is free software; you can redistribute it and/or
6+
* modify it under the terms of the GNU Lesser General Public
7+
* License as published by the Free Software Foundation version 2.1
8+
* of the License.
9+
*
10+
* This library is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13+
* Lesser General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU Lesser General Public
16+
* License along with this library; if not, write to the Free Software
17+
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18+
*/
19+
20+
package com.patchworkmc.impl.enumhacks;
21+
22+
import net.minecraftforge.common.util.TriPredicate;
23+
24+
import net.minecraft.entity.EntityType;
25+
import net.minecraft.util.math.BlockPos;
26+
import net.minecraft.world.ViewableWorld;
27+
28+
public interface PatchworkSpawnRestrictionLocation {
29+
boolean patchwork_useVanillaBehavior();
30+
31+
void patchwork_setPredicate(TriPredicate<ViewableWorld, BlockPos, EntityType<?>> predicate);
32+
33+
// Forge method, so no prefix
34+
boolean canSpawnAt(ViewableWorld world, BlockPos pos, EntityType<?> type);
35+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* Minecraft Forge, Patchwork Project
3+
* Copyright (c) 2016-2020, 2019-2020
4+
*
5+
* This library is free software; you can redistribute it and/or
6+
* modify it under the terms of the GNU Lesser General Public
7+
* License as published by the Free Software Foundation version 2.1
8+
* of the License.
9+
*
10+
* This library is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13+
* Lesser General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU Lesser General Public
16+
* License along with this library; if not, write to the Free Software
17+
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18+
*/
19+
20+
package com.patchworkmc.mixin.enumhacks;
21+
22+
import org.spongepowered.asm.mixin.Mixin;
23+
import org.spongepowered.asm.mixin.gen.Invoker;
24+
25+
import net.minecraft.block.entity.BannerPattern;
26+
import net.minecraft.item.ItemStack;
27+
28+
@Mixin(BannerPattern.class)
29+
public interface BannerPatternAccessor {
30+
@Invoker("<init>")
31+
static BannerPattern invokeConstructor(String constantName, int ordinal, String name, String id, ItemStack baseStack) {
32+
throw new IllegalStateException("Mixin did not transform accessor! Something is very wrong!");
33+
}
34+
35+
@Invoker("<init>")
36+
static BannerPattern invokeConstructor(String constantName, int ordinal, String name, String id, String recipePattern0, String recipePattern1, String recipePattern2) {
37+
throw new IllegalStateException("Mixin did not transform accessor! Something is very wrong!");
38+
}
39+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*
2+
* Minecraft Forge, Patchwork Project
3+
* Copyright (c) 2016-2020, 2019-2020
4+
*
5+
* This library is free software; you can redistribute it and/or
6+
* modify it under the terms of the GNU Lesser General Public
7+
* License as published by the Free Software Foundation version 2.1
8+
* of the License.
9+
*
10+
* This library is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13+
* Lesser General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU Lesser General Public
16+
* License along with this library; if not, write to the Free Software
17+
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18+
*/
19+
20+
package com.patchworkmc.mixin.enumhacks;
21+
22+
import org.spongepowered.asm.mixin.Mixin;
23+
import org.spongepowered.asm.mixin.gen.Invoker;
24+
25+
import net.minecraft.entity.EntityCategory;
26+
27+
@Mixin(EntityCategory.class)
28+
public interface EntityCategoryAccessor {
29+
@Invoker("<init>")
30+
static EntityCategory invokeConstructor(String constantName, int index, String name, int spawnCap, boolean peaceful, boolean animal) {
31+
throw new IllegalStateException("Mixin did not transform accessor! Something is very wrong!");
32+
}
33+
}

0 commit comments

Comments
 (0)