Capability rework to get rid of @CapabilityInject and use CapabilityToken as the identifier, deprecated old system for removal in 1.18. (#8116)

This commit is contained in:
LexManos authored and GitHub committed 2021-09-30 22:54:23 -07:00
1 parent 166feefc02
commit 96303f6570
12 files changed
+285 -67

No files matched your search

@@ -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<T>
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<T> addListener(Consumer<Capability<T>> listener)
{
if (this.isRegistered())
listener.accept(this);
else
this.listeners.add(listener);
return this;
}
// INTERNAL
private final String name;
List<Consumer<Capability<T>>> listeners = new ArrayList<>();
Capability(String name)
{
this.name = name;
}
void onRegister()
{
var listeners = this.listeners;
this.listeners = null;
listeners.forEach(l -> l.accept(this));
}
}
@@ -45,9 +45,12 @@ import java.lang.annotation.*;
*
* <b>Warning</b>: 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
{
/**
@@ -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 <T> void register(Class<T> type)
{
Objects.requireNonNull(type,"Attempted to register a capability with invalid type");
String realName = type.getName().intern();
get(Type.getInternalName(type), true);
}
public static <T> Capability<T> get(CapabilityToken<T> type)
{
return INSTANCE.get(type.getType(), false);
}
@SuppressWarnings("unchecked")
<T> Capability<T> get(String realName, boolean registering)
{
Capability<T> 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<T>)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<String, Capability<?>> providers = new IdentityHashMap<>();
private volatile IdentityHashMap<String, List<Function<Capability<?>, Object>>> callbacks;
public void injectCapabilities(List<ModFileScanData> data)
{
final List<ModFileScanData.AnnotationData> 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<String, List<Function<Capability<?>, Object>>> callbacks, Iterable<Capability<?>> caps)
{
for (var cap : caps)
{
callbacks.getOrDefault(cap.getName(), List.of()).forEach(f -> f.apply(cap));
}
}
private static void gatherCallbacks(Map<String, List<Function<Capability<?>, Object>>> callbacks, ModFileScanData.AnnotationData annotationData)
private void gatherCallbacks(Map<String, List<Function<Capability<?>, 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<Function<Capability<?>, 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;
});
}
}
@@ -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:
* <pre>{@code
* public static Capability<IDataHolder> DATA_HOLDER_CAPABILITY
* = CapabilityManager.get(new CapabilityToken<>(){});
*
* </pre>
*
*/
public abstract class CapabilityToken<T>
{
protected final String getType()
{
throw new RuntimeException("This will be implemented by a transformer");
}
@Override
public String toString()
{
return "CapabilityToken[" + getType() + "]";
}
}
@@ -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<String, Capability<?>> 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 <T> void register(Class<T> 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<String, Capability<?>> getCapabilities()
{
return this.capabilities;
}
}
@@ -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<IAnimationStateMachine> ANIMATION_CAPABILITY = null;
public static Capability<IAnimationStateMachine> ANIMATION_CAPABILITY = CapabilityManager.get(new CapabilityToken<>(){});
public static void register(RegisterCapabilitiesEvent event)
{
@@ -23,8 +23,7 @@ import net.minecraftforge.common.capabilities.*;
public class CapabilityEnergy
{
@CapabilityInject(IEnergyStorage.class)
public static Capability<IEnergyStorage> ENERGY = null;
public static final Capability<IEnergyStorage> ENERGY = CapabilityManager.get(new CapabilityToken<>(){});;
public static void register(RegisterCapabilitiesEvent event)
{
@@ -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<IFluidHandler> FLUID_HANDLER_CAPABILITY = null;
@CapabilityInject(IFluidHandlerItem.class)
public static Capability<IFluidHandlerItem> FLUID_HANDLER_ITEM_CAPABILITY = null;
public static Capability<IFluidHandler> FLUID_HANDLER_CAPABILITY = CapabilityManager.get(new CapabilityToken<>(){});
public static Capability<IFluidHandlerItem> FLUID_HANDLER_ITEM_CAPABILITY = CapabilityManager.get(new CapabilityToken<>(){});
public static void register(RegisterCapabilitiesEvent event)
{
@@ -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<IItemHandler> ITEM_HANDLER_CAPABILITY = null;
public static final Capability<IItemHandler> ITEM_HANDLER_CAPABILITY = CapabilityManager.get(new CapabilityToken<>(){});
public static void register(RegisterCapabilitiesEvent event)
{