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)
{