diff --git a/fmlloader/src/main/java/net/minecraftforge/common/asm/CapabilityInjectDefinalize.java b/fmlloader/src/main/java/net/minecraftforge/common/asm/CapabilityInjectDefinalize.java index c3344786fd..9c9c8ee12c 100644 --- a/fmlloader/src/main/java/net/minecraftforge/common/asm/CapabilityInjectDefinalize.java +++ b/fmlloader/src/main/java/net/minecraftforge/common/asm/CapabilityInjectDefinalize.java @@ -19,7 +19,6 @@ package net.minecraftforge.common.asm; -import java.nio.file.Path; import java.util.EnumSet; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; @@ -33,6 +32,7 @@ import cpw.mods.modlauncher.serviceapi.ILaunchPluginService; /** * Removes the final modifier from fields with the @CapabilityInject annotation, prevents the JITer from in lining them so our runtime replacements can work. */ +@Deprecated(since = "1.18", forRemoval = true) public class CapabilityInjectDefinalize implements ILaunchPluginService { private final String CAP = "Lnet/minecraftforge/common/capabilities/Capability;"; //Don't directly reference this to prevent class loading. diff --git a/fmlloader/src/main/java/net/minecraftforge/common/asm/CapabilityTokenSubclass.java b/fmlloader/src/main/java/net/minecraftforge/common/asm/CapabilityTokenSubclass.java new file mode 100644 index 0000000000..d64e8bcd9e --- /dev/null +++ b/fmlloader/src/main/java/net/minecraftforge/common/asm/CapabilityTokenSubclass.java @@ -0,0 +1,134 @@ +/* + * Minecraft Forge + * Copyright (c) 2016-2021. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation version 2.1 + * of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package net.minecraftforge.common.asm; + +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.EnumSet; + +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; +import org.objectweb.asm.signature.SignatureReader; +import org.objectweb.asm.signature.SignatureVisitor; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.MethodNode; + +import cpw.mods.modlauncher.serviceapi.ILaunchPluginService; + +/** + * Implements getType() in CapabilityToken subclasses. + * + * Using the class's signature to determine the generic type of TypeToken, and then implements getType() using that value. + *
+ * Example:
+ *
+ *  new CapabilityToken<String>(){}
+ *  Has the signature "CapabilityToken<Ljava/lang/String;>"
+ *
+ *  Implements the method:
+ *  public String getType() {
+ *    return "java/lang/String";
+ *  }
+ * 
+ */ +public class CapabilityTokenSubclass implements ILaunchPluginService { + + private final String FUNC_NAME = "getType"; + private final String FUNC_DESC = "()Ljava/lang/String;"; + private final String CAP_INJECT = "net/minecraftforge/common/capabilities/CapabilityToken"; //Don't directly reference this to prevent class loading. + + @Override + public String name() { + return "capability_token_subclass"; + } + + private static final EnumSet YAY = EnumSet.of(Phase.AFTER); + private static final EnumSet NAY = EnumSet.noneOf(Phase.class); + + @Override + public EnumSet handlesClass(Type classType, boolean isEmpty) + { + return isEmpty ? NAY : YAY; + } + + @Override + public int processClassWithFlags(final Phase phase, final ClassNode classNode, final Type classType, final String reason) + { + if (CAP_INJECT.equals(classNode.name)) + { + for (MethodNode mtd : classNode.methods) + { + if (FUNC_NAME.equals(mtd.name) && FUNC_DESC.equals(mtd.desc)) + { + mtd.access &= ~Opcodes.ACC_FINAL; // We have it final in code so people don't override it, cuz that'd be stupid, and make our transformer more complicated. + } + } + return ComputeFlags.SIMPLE_REWRITE; + } + else if (CAP_INJECT.equals(classNode.superName)) + { + Holder cls = new Holder(); + + SignatureReader reader = new SignatureReader(classNode.signature); // Having a node version of this would probably be useful. + reader.accept(new SignatureVisitor(Opcodes.ASM9) + { + Deque stack = new ArrayDeque<>(); + + @Override + public void visitClassType(final String name) + { + stack.push(name); + } + + @Override + public void visitInnerClassType(final String name) + { + stack.push(stack.pop() + '$' + name); + } + + @Override + public void visitEnd() + { + var val = stack.pop(); + if (!stack.isEmpty() && CAP_INJECT.equals(stack.peek())) + cls.value = val; + } + }); + + if (cls.value == null) + throw new IllegalStateException("Could not find signature for CapabilityToken on " + classNode.name + " from " + classNode.signature); + + var mtd = classNode.visitMethod(Opcodes.ACC_PUBLIC, FUNC_NAME, FUNC_DESC, null, new String[0]); + mtd.visitLdcInsn(cls.value); + mtd.visitInsn(Opcodes.ARETURN); + mtd.visitEnd(); + return ComputeFlags.COMPUTE_MAXS; + } + else + { + return ComputeFlags.NO_REWRITE; + } + } + + private static class Holder { + String value; + } + +} diff --git a/fmlloader/src/main/resources/META-INF/services/cpw.mods.modlauncher.serviceapi.ILaunchPluginService b/fmlloader/src/main/resources/META-INF/services/cpw.mods.modlauncher.serviceapi.ILaunchPluginService index 3771972299..cf2cc7e041 100644 --- a/fmlloader/src/main/resources/META-INF/services/cpw.mods.modlauncher.serviceapi.ILaunchPluginService +++ b/fmlloader/src/main/resources/META-INF/services/cpw.mods.modlauncher.serviceapi.ILaunchPluginService @@ -1,4 +1,5 @@ net.minecraftforge.fml.loading.RuntimeDistCleaner net.minecraftforge.common.asm.RuntimeEnumExtender net.minecraftforge.common.asm.ObjectHolderDefinalize -net.minecraftforge.common.asm.CapabilityInjectDefinalize \ No newline at end of file +net.minecraftforge.common.asm.CapabilityInjectDefinalize +net.minecraftforge.common.asm.CapabilityTokenSubclass \ No newline at end of file diff --git a/src/main/java/net/minecraftforge/common/capabilities/Capability.java b/src/main/java/net/minecraftforge/common/capabilities/Capability.java index 6caa3765bd..5b56e49905 100644 --- a/src/main/java/net/minecraftforge/common/capabilities/Capability.java +++ b/src/main/java/net/minecraftforge/common/capabilities/Capability.java @@ -21,6 +21,10 @@ package net.minecraftforge.common.capabilities; import net.minecraftforge.common.util.LazyOptional; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + import javax.annotation.Nonnull; /** @@ -43,11 +47,44 @@ public class Capability return this == toCheck ? inst.cast() : LazyOptional.empty(); } + /** + * @return true if something has registered this capability to the Manager. + * This is a marker that the class for this capability exists, and can be used. + */ + public boolean isRegistered() + { + return this.listeners == null; + } + + /** + * Adds a listener to be called when someone registers this capability. + * May be called instantly if this is already registered. + * + * @param listener Function to fire when capability is registered. + * @return self, in case people want to use builder pattern. + */ + public synchronized Capability addListener(Consumer> listener) + { + if (this.isRegistered()) + listener.accept(this); + else + this.listeners.add(listener); + return this; + } + // INTERNAL private final String name; + List>> listeners = new ArrayList<>(); Capability(String name) { this.name = name; } + + void onRegister() + { + var listeners = this.listeners; + this.listeners = null; + listeners.forEach(l -> l.accept(this)); + } } diff --git a/src/main/java/net/minecraftforge/common/capabilities/CapabilityInject.java b/src/main/java/net/minecraftforge/common/capabilities/CapabilityInject.java index d22a52bf99..bf24e4f365 100644 --- a/src/main/java/net/minecraftforge/common/capabilities/CapabilityInject.java +++ b/src/main/java/net/minecraftforge/common/capabilities/CapabilityInject.java @@ -45,9 +45,12 @@ import java.lang.annotation.*; * * Warning: Capability injections are run in the thread that the capablity is registered. * Due to parallel mod loading, this can potentially be off of the main thread. + * + * @deprecated Use {@link CapabilityManager#get(CapabilityToken)} */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD, ElementType.METHOD}) +@Deprecated(since = "1.18", forRemoval = true) public @interface CapabilityInject { /** diff --git a/src/main/java/net/minecraftforge/common/capabilities/CapabilityManager.java b/src/main/java/net/minecraftforge/common/capabilities/CapabilityManager.java index bd82c2276c..a6e5344fa7 100644 --- a/src/main/java/net/minecraftforge/common/capabilities/CapabilityManager.java +++ b/src/main/java/net/minecraftforge/common/capabilities/CapabilityManager.java @@ -22,7 +22,6 @@ package net.minecraftforge.common.capabilities; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; -import java.util.ArrayList; import java.util.Collection; import java.util.IdentityHashMap; import java.util.List; @@ -55,30 +54,53 @@ public enum CapabilityManager * @param type The class type to be registered * @deprecated use {@link RegisterCapabilitiesEvent} */ - @Deprecated(forRemoval = true) + @Deprecated(since = "1.18", forRemoval = true) public void register(Class type) { Objects.requireNonNull(type,"Attempted to register a capability with invalid type"); - String realName = type.getName().intern(); + get(Type.getInternalName(type), true); + } + + + public static Capability get(CapabilityToken type) + { + return INSTANCE.get(type.getType(), false); + } + + @SuppressWarnings("unchecked") + Capability get(String realName, boolean registering) + { Capability cap; synchronized (providers) { - if (providers.containsKey(realName)) { - LOGGER.error(CAPABILITIES, "Cannot register capability implementation multiple times : {}", realName); - throw new IllegalArgumentException("Cannot register a capability implementation multiple times : "+ realName); - } + realName = realName.intern(); + cap = (Capability)providers.computeIfAbsent(realName, Capability::new); - cap = new Capability<>(realName); - providers.put(realName, cap); } - fireCallbacks(this.callbacks, List.of(cap)); + + if (registering) + { + synchronized (cap) + { + if (cap.isRegistered()) + { + LOGGER.error(CAPABILITIES, "Cannot register capability implementation multiple times : {}", realName); + throw new IllegalArgumentException("Cannot register a capability implementation multiple times : "+ realName); + } + else + { + cap.onRegister(); + } + } + } + + return cap; } // INTERNAL private final IdentityHashMap> providers = new IdentityHashMap<>(); - private volatile IdentityHashMap, Object>>> callbacks; public void injectCapabilities(List data) { final List elementsToInject = data.stream() @@ -91,22 +113,9 @@ public enum CapabilityManager var event = new RegisterCapabilitiesEvent(); ModLoader.get().postEvent(event); - fireCallbacks(callbacks, event.getCapabilities().values()); - - // TODO 1.18: remove these fields - this.providers.putAll(event.getCapabilities()); - this.callbacks = callbacks; } - private static void fireCallbacks(Map, Object>>> callbacks, Iterable> caps) - { - for (var cap : caps) - { - callbacks.getOrDefault(cap.getName(), List.of()).forEach(f -> f.apply(cap)); - } - } - - private static void gatherCallbacks(Map, Object>>> callbacks, ModFileScanData.AnnotationData annotationData) + private void gatherCallbacks(Map, Object>>> callbacks, ModFileScanData.AnnotationData annotationData) { final String targetClass = annotationData.clazz().getClassName(); final String targetName = annotationData.memberName(); @@ -116,13 +125,14 @@ public enum CapabilityManager LOGGER.warn(CAPABILITIES,"Unable to inject capability at {}.{} (Invalid Annotation)", targetClass, targetName); return; } - final String capabilityName = type.getInternalName().replace('/', '.').intern(); + final String capabilityName = type.getInternalName(); - List, Object>> list = callbacks.computeIfAbsent(capabilityName, k -> new ArrayList<>()); + final Capability cap = get(capabilityName, false); if (annotationData.memberName().indexOf('(') > 0) { - list.add(input -> { + cap.addListener(input -> + { try { for (Method mtd : Class.forName(targetClass).getDeclaredMethods()) @@ -132,12 +142,12 @@ public enum CapabilityManager if ((mtd.getModifiers() & Modifier.STATIC) != Modifier.STATIC) { LOGGER.warn(CAPABILITIES,"Unable to inject capability {} at {}.{} (Non-Static)", capabilityName, targetClass, targetName); - return null; + return; } mtd.setAccessible(true); mtd.invoke(null, input); - return null; + return; } } LOGGER.warn(CAPABILITIES,"Unable to inject capability {} at {}.{} (Method Not Found)", capabilityName, targetClass, targetName); @@ -146,19 +156,19 @@ public enum CapabilityManager { LOGGER.warn(CAPABILITIES,"Unable to inject capability {} at {}.{}", capabilityName, targetClass, targetName, e); } - return null; }); } else { - list.add(input -> { + cap.addListener(input -> + { try { Field field = Class.forName(targetClass).getDeclaredField(targetName); if ((field.getModifiers() & Modifier.STATIC) != Modifier.STATIC) { LOGGER.warn(CAPABILITIES,"Unable to inject capability {} at {}.{} (Non-Static)", capabilityName, targetClass, targetName); - return null; + return; } field.setAccessible(true); field.set(null, input); @@ -167,7 +177,6 @@ public enum CapabilityManager { LOGGER.warn(CAPABILITIES,"Unable to inject capability {} at {}.{}", capabilityName, targetClass, targetName, e); } - return null; }); } } diff --git a/src/main/java/net/minecraftforge/common/capabilities/CapabilityToken.java b/src/main/java/net/minecraftforge/common/capabilities/CapabilityToken.java new file mode 100644 index 0000000000..19e3466474 --- /dev/null +++ b/src/main/java/net/minecraftforge/common/capabilities/CapabilityToken.java @@ -0,0 +1,49 @@ +/* + * Minecraft Forge + * Copyright (c) 2016-2021. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation version 2.1 + * of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package net.minecraftforge.common.capabilities; + +/** + * Inspired by {@link com.google.common.reflect.TypeToken TypeToken}, use a subclass to capture + * generic types. Then uses {@link net.minecraftforge.common.asm.CapabilityTokenSubclass a transformer} + * to convert that generic into a string returned by {@link #getType} + * This allows us to know the generic type, without having a hard reference to the + * class. + * + * Example usage: + *
{@code
+ *    public static Capability DATA_HOLDER_CAPABILITY
+ *    		= CapabilityManager.get(new CapabilityToken<>(){});
+ *
+ * 
+ * + */ +public abstract class CapabilityToken +{ + protected final String getType() + { + throw new RuntimeException("This will be implemented by a transformer"); + } + + @Override + public String toString() + { + return "CapabilityToken[" + getType() + "]"; + } +} diff --git a/src/main/java/net/minecraftforge/common/capabilities/RegisterCapabilitiesEvent.java b/src/main/java/net/minecraftforge/common/capabilities/RegisterCapabilitiesEvent.java index ae7fcc604e..6eaf3fbd52 100644 --- a/src/main/java/net/minecraftforge/common/capabilities/RegisterCapabilitiesEvent.java +++ b/src/main/java/net/minecraftforge/common/capabilities/RegisterCapabilitiesEvent.java @@ -19,22 +19,19 @@ package net.minecraftforge.common.capabilities; +import java.util.Objects; + +import org.objectweb.asm.Type; + import net.minecraftforge.eventbus.api.Event; import net.minecraftforge.fml.event.IModBusEvent; -import java.util.*; - -import static net.minecraftforge.fml.Logging.CAPABILITIES; - /** * This event fires when it is time to register your capabilities. * @see Capability */ public final class RegisterCapabilitiesEvent extends Event implements IModBusEvent { - - private final IdentityHashMap> capabilities = new IdentityHashMap<>(); - /** * Registers a capability to be consumed by others. * APIs who define the capability should call this. @@ -45,16 +42,6 @@ public final class RegisterCapabilitiesEvent extends Event implements IModBusEve public void register(Class type) { Objects.requireNonNull(type,"Attempted to register a capability with invalid type"); - String realName = type.getName().intern(); - if (capabilities.putIfAbsent(realName, new Capability<>(realName)) != null) { - CapabilityManager.LOGGER.error(CAPABILITIES, "Cannot register capability implementation multiple times : {}", realName); - throw new IllegalArgumentException("Cannot register a capability implementation multiple times : "+ realName); - } + CapabilityManager.INSTANCE.get(Type.getInternalName(type), true); } - - IdentityHashMap> getCapabilities() - { - return this.capabilities; - } - } \ No newline at end of file diff --git a/src/main/java/net/minecraftforge/common/model/animation/CapabilityAnimation.java b/src/main/java/net/minecraftforge/common/model/animation/CapabilityAnimation.java index b86d806ed8..9d860ccd83 100644 --- a/src/main/java/net/minecraftforge/common/model/animation/CapabilityAnimation.java +++ b/src/main/java/net/minecraftforge/common/model/animation/CapabilityAnimation.java @@ -21,7 +21,8 @@ package net.minecraftforge.common.model.animation; import net.minecraft.core.Direction; import net.minecraftforge.common.capabilities.Capability; -import net.minecraftforge.common.capabilities.CapabilityInject; +import net.minecraftforge.common.capabilities.CapabilityManager; +import net.minecraftforge.common.capabilities.CapabilityToken; import net.minecraftforge.common.capabilities.RegisterCapabilitiesEvent; import net.minecraftforge.common.capabilities.ICapabilityProvider; import net.minecraftforge.common.util.LazyOptional; @@ -31,8 +32,7 @@ import javax.annotation.Nullable; public class CapabilityAnimation { - @CapabilityInject(IAnimationStateMachine.class) - public static Capability ANIMATION_CAPABILITY = null; + public static Capability ANIMATION_CAPABILITY = CapabilityManager.get(new CapabilityToken<>(){}); public static void register(RegisterCapabilitiesEvent event) { diff --git a/src/main/java/net/minecraftforge/energy/CapabilityEnergy.java b/src/main/java/net/minecraftforge/energy/CapabilityEnergy.java index 214abfd30f..68002a1061 100644 --- a/src/main/java/net/minecraftforge/energy/CapabilityEnergy.java +++ b/src/main/java/net/minecraftforge/energy/CapabilityEnergy.java @@ -23,8 +23,7 @@ import net.minecraftforge.common.capabilities.*; public class CapabilityEnergy { - @CapabilityInject(IEnergyStorage.class) - public static Capability ENERGY = null; + public static final Capability ENERGY = CapabilityManager.get(new CapabilityToken<>(){});; public static void register(RegisterCapabilitiesEvent event) { diff --git a/src/main/java/net/minecraftforge/fluids/capability/CapabilityFluidHandler.java b/src/main/java/net/minecraftforge/fluids/capability/CapabilityFluidHandler.java index 3963c9e6c7..2d1ba1fcdf 100644 --- a/src/main/java/net/minecraftforge/fluids/capability/CapabilityFluidHandler.java +++ b/src/main/java/net/minecraftforge/fluids/capability/CapabilityFluidHandler.java @@ -20,15 +20,14 @@ package net.minecraftforge.fluids.capability; import net.minecraftforge.common.capabilities.Capability; -import net.minecraftforge.common.capabilities.CapabilityInject; +import net.minecraftforge.common.capabilities.CapabilityManager; +import net.minecraftforge.common.capabilities.CapabilityToken; import net.minecraftforge.common.capabilities.RegisterCapabilitiesEvent; public class CapabilityFluidHandler { - @CapabilityInject(IFluidHandler.class) - public static Capability FLUID_HANDLER_CAPABILITY = null; - @CapabilityInject(IFluidHandlerItem.class) - public static Capability FLUID_HANDLER_ITEM_CAPABILITY = null; + public static Capability FLUID_HANDLER_CAPABILITY = CapabilityManager.get(new CapabilityToken<>(){}); + public static Capability FLUID_HANDLER_ITEM_CAPABILITY = CapabilityManager.get(new CapabilityToken<>(){}); public static void register(RegisterCapabilitiesEvent event) { diff --git a/src/main/java/net/minecraftforge/items/CapabilityItemHandler.java b/src/main/java/net/minecraftforge/items/CapabilityItemHandler.java index 4229dc9079..d4905c47ba 100644 --- a/src/main/java/net/minecraftforge/items/CapabilityItemHandler.java +++ b/src/main/java/net/minecraftforge/items/CapabilityItemHandler.java @@ -20,13 +20,13 @@ package net.minecraftforge.items; import net.minecraftforge.common.capabilities.Capability; -import net.minecraftforge.common.capabilities.CapabilityInject; +import net.minecraftforge.common.capabilities.CapabilityManager; +import net.minecraftforge.common.capabilities.CapabilityToken; import net.minecraftforge.common.capabilities.RegisterCapabilitiesEvent; public class CapabilityItemHandler { - @CapabilityInject(IItemHandler.class) - public static Capability ITEM_HANDLER_CAPABILITY = null; + public static final Capability ITEM_HANDLER_CAPABILITY = CapabilityManager.get(new CapabilityToken<>(){}); public static void register(RegisterCapabilitiesEvent event) {