From 8e0845d032e18b0999ec0221ba73cb4d7325ada7 Mon Sep 17 00:00:00 2001 From: LexManos Date: Wed, 19 Jun 2024 15:30:46 -0700 Subject: [PATCH] Add support for named sub-caps --- .../common/asm/CapabilityTokenSubclass.java | 36 ++----- .../common/ForgeStatesProvider.java | 8 +- .../common/capabilities/Capability.java | 22 ++-- .../capabilities/CapabilityManager.java | 102 +++++++++++------- .../common/capabilities/CapabilityToken.java | 9 +- .../RegisterCapabilitiesEvent.java | 18 ++-- 6 files changed, 102 insertions(+), 93 deletions(-) diff --git a/fmlloader/src/main/java/net/minecraftforge/fml/common/asm/CapabilityTokenSubclass.java b/fmlloader/src/main/java/net/minecraftforge/fml/common/asm/CapabilityTokenSubclass.java index 7ed6bfbe5d..594c1ee28c 100644 --- a/fmlloader/src/main/java/net/minecraftforge/fml/common/asm/CapabilityTokenSubclass.java +++ b/fmlloader/src/main/java/net/minecraftforge/fml/common/asm/CapabilityTokenSubclass.java @@ -49,49 +49,38 @@ public class CapabilityTokenSubclass implements ILaunchPluginService { private static final EnumSet NAY = EnumSet.noneOf(Phase.class); @Override - public EnumSet handlesClass(Type classType, boolean isEmpty) - { + 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)) - { + 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)) - { + } 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) - { + reader.accept(new SignatureVisitor(Opcodes.ASM9) { Deque stack = new ArrayDeque<>(); @Override - public void visitClassType(final String name) - { + public void visitClassType(final String name) { stack.push(name); } @Override - public void visitInnerClassType(final String name) - { + public void visitInnerClassType(final String name) { stack.push(stack.pop() + '$' + name); } @Override - public void visitEnd() - { + public void visitEnd() { var val = stack.pop(); if (!stack.isEmpty() && CAP_INJECT.equals(stack.peek())) cls.value = val; @@ -106,9 +95,7 @@ public class CapabilityTokenSubclass implements ILaunchPluginService { mtd.visitInsn(Opcodes.ARETURN); mtd.visitEnd(); return ComputeFlags.COMPUTE_MAXS; - } - else - { + } else { return ComputeFlags.NO_REWRITE; } } @@ -116,5 +103,4 @@ public class CapabilityTokenSubclass implements ILaunchPluginService { private static class Holder { String value; } - } diff --git a/src/main/java/net/minecraftforge/common/ForgeStatesProvider.java b/src/main/java/net/minecraftforge/common/ForgeStatesProvider.java index fb7ac091c2..9f1b1f6af4 100644 --- a/src/main/java/net/minecraftforge/common/ForgeStatesProvider.java +++ b/src/main/java/net/minecraftforge/common/ForgeStatesProvider.java @@ -16,19 +16,17 @@ import net.minecraftforge.registries.GameData; import net.minecraftforge.registries.ObjectHolderRegistry; import net.minecraftforge.registries.RegistryManager; -public class ForgeStatesProvider implements IModStateProvider -{ +public class ForgeStatesProvider implements IModStateProvider { final ModLoadingState CREATE_REGISTRIES = ModLoadingState.withInline("CREATE_REGISTRIES", "CONSTRUCT", ModLoadingPhase.GATHER, ml -> RegistryManager.postNewRegistryEvent()); final ModLoadingState OBJECT_HOLDERS = ModLoadingState.withInline("OBJECT_HOLDERS", "CREATE_REGISTRIES", ModLoadingPhase.GATHER, ml -> ObjectHolderRegistry.findObjectHolders()); - final ModLoadingState INJECT_CAPABILITIES = ModLoadingState.withInline("INJECT_CAPABILITIES", "OBJECT_HOLDERS", ModLoadingPhase.GATHER, ml -> CapabilityManager.INSTANCE.injectCapabilities(ml.getAllScanData())); + final ModLoadingState INJECT_CAPABILITIES = ModLoadingState.withInline("INJECT_CAPABILITIES", "OBJECT_HOLDERS", ModLoadingPhase.GATHER, CapabilityManager::injectCapabilities); final ModLoadingState UNFREEZE = ModLoadingState.withInline("UNFREEZE_DATA", "INJECT_CAPABILITIES", ModLoadingPhase.GATHER, ml -> GameData.unfreezeData()); final ModLoadingState LOAD_REGISTRIES = ModLoadingState.withInline("LOAD_REGISTRIES", "UNFREEZE_DATA", ModLoadingPhase.GATHER, ml -> GameData.postRegisterEvents()); final ModLoadingState FREEZE = ModLoadingState.withInline("FREEZE_DATA", "COMPLETE", ModLoadingPhase.COMPLETE, ml -> GameData.freezeData()); final ModLoadingState NETLOCK = ModLoadingState.withInline("NETWORK_LOCK", "FREEZE_DATA", ModLoadingPhase.COMPLETE, ml -> NetworkRegistry.lock()); @Override - public List getAllStates() - { + public List getAllStates() { return List.of(CREATE_REGISTRIES, OBJECT_HOLDERS, INJECT_CAPABILITIES, UNFREEZE, LOAD_REGISTRIES, FREEZE, NETLOCK); } } diff --git a/src/main/java/net/minecraftforge/common/capabilities/Capability.java b/src/main/java/net/minecraftforge/common/capabilities/Capability.java index 3078f03447..8839a3aad7 100644 --- a/src/main/java/net/minecraftforge/common/capabilities/Capability.java +++ b/src/main/java/net/minecraftforge/common/capabilities/Capability.java @@ -19,16 +19,16 @@ import java.util.function.Consumer; * * The CapabilityManager is in charge of creating this class. */ -public class Capability -{ +public final class Capability { /** * @return The unique name of this capability, typically this is * the fully qualified class name for the target interface. */ - public String getName() { return name; } + public String getName() { + return name; + } - public @NotNull LazyOptional orEmpty(Capability toCheck, LazyOptional inst) - { + public @NotNull LazyOptional orEmpty(Capability toCheck, LazyOptional inst) { return this == toCheck ? inst.cast() : LazyOptional.empty(); } @@ -36,8 +36,7 @@ public class Capability * @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() - { + public boolean isRegistered() { return this.listeners == null; } @@ -48,8 +47,7 @@ public class Capability * @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) - { + public synchronized Capability addListener(Consumer> listener) { if (this.isRegistered()) listener.accept(this); else @@ -61,13 +59,11 @@ public class Capability private final String name; List>> listeners = new ArrayList<>(); - Capability(String name) - { + Capability(String name) { this.name = name; } - void onRegister() - { + 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/CapabilityManager.java b/src/main/java/net/minecraftforge/common/capabilities/CapabilityManager.java index 2bd10c6d8b..47f2830424 100644 --- a/src/main/java/net/minecraftforge/common/capabilities/CapabilityManager.java +++ b/src/main/java/net/minecraftforge/common/capabilities/CapabilityManager.java @@ -5,65 +5,90 @@ package net.minecraftforge.common.capabilities; +import net.minecraft.resources.ResourceLocation; import net.minecraftforge.fml.Logging; +import net.minecraftforge.fml.ModList; import net.minecraftforge.fml.ModLoader; -import net.minecraftforge.forgespi.language.ModFileScanData; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.jetbrains.annotations.ApiStatus; import org.objectweb.asm.Type; import java.util.Comparator; -import java.util.IdentityHashMap; -import java.util.List; +import java.util.HashMap; +import java.util.Map; -public enum CapabilityManager -{ - INSTANCE; - static final Logger LOGGER = LogManager.getLogger(); +public final class CapabilityManager { + @Deprecated(forRemoval = true, since = "1.21") + public static final CapabilityManager INSTANCE = new CapabilityManager(); + private static final Logger LOGGER = LogManager.getLogger(); - public static Capability get(CapabilityToken type) - { - return INSTANCE.get(type.getType(), false); + /** + * Retrieves the 'root' capability for the specified type. This is the generic one that is used when the API + * registers the specified type. It is also safe to assume that if your child token is a valid return when + * this Capability is requested. + */ + public static Capability get(CapabilityToken type) { + return get(type.getType(), null, false); + } + + /** + * Retrieves a named capability of the specified type. This is useful when providers supply multiple instances of the same interface and want to + * distinguish between them. It is generally not safe to return your generic implementation for the root type when this is requested. + */ + public static Capability get(CapabilityToken type, String modid, String name) { + return get(type, ResourceLocation.fromNamespaceAndPath(modid, name)); + } + + /** + * Retrieves a named capability of the specified type. This is useful when providers supply multiple instances of the same interface and want to + * distinguish between them. It is generally not safe to return your generic implementation for the root type when this is requested. + */ + public static Capability get(CapabilityToken type, ResourceLocation name) { + return get(type.getType(), name, false); } @SuppressWarnings("unchecked") - Capability get(String realName, boolean registering) - { + static Capability get(String type, ResourceLocation name, boolean registering) { Capability cap; - synchronized (providers) - { - realName = realName.intern(); - cap = (Capability)providers.computeIfAbsent(realName, Capability::new); + synchronized (providers) { + final var parent = (Capability)providers.computeIfAbsent(new Key(type, null), k -> new Capability<>(type.intern())); + + if (name == null) { + cap = parent; + } else { // A Named child + cap = (Capability)providers.computeIfAbsent(new Key(type, name), k -> { + var ret = new Capability<>((parent.getName() + '#' + name.toString()).intern()); + parent.addListener(p -> ret.onRegister()); + return ret; + }); + } } + if (registering) { + if (name != null) + error("Cannot register capability type " + type + " + with a name: " + name); - if (registering) - { - synchronized (cap) - { + synchronized (cap) { if (cap.isRegistered()) - { - LOGGER.error(Logging.CAPABILITIES, "Cannot register capability implementation multiple times : {}", realName); - throw new IllegalArgumentException("Cannot register a capability implementation multiple times : "+ realName); - } - else - { - cap.onRegister(); - } + error("Cannot register capability type " + type + " multiple times"); + + cap.onRegister(); } } return cap; } - // INTERNAL private static final Type AUTO_REGISTER = Type.getType(AutoRegisterCapability.class); - private final IdentityHashMap> providers = new IdentityHashMap<>(); - public void injectCapabilities(List data) - { - var autos = data.stream() + private record Key(String type, ResourceLocation name) {}; + private static final Map> providers = new HashMap<>(); + + @ApiStatus.Internal + public static void injectCapabilities(ModList modlist) { + var autos = modlist.getAllScanData().stream() .flatMap(e -> e.getAnnotations().stream()) .filter(a -> AUTO_REGISTER.equals(a.annotationType())) .map(a -> a.clazz()) @@ -71,13 +96,18 @@ public enum CapabilityManager .sorted(Comparator.comparing(Type::toString)) .toList(); - for (var auto : autos) - { + for (var auto : autos) { LOGGER.debug(Logging.CAPABILITIES, "Attempting to automatically register: " + auto); - get(auto.getInternalName(), true); + get(auto.getInternalName(), null, true); } + @SuppressWarnings("removal") var event = new RegisterCapabilitiesEvent(); ModLoader.get().postEvent(event); } + + private static void error(String message) { + LOGGER.error(Logging.CAPABILITIES, message); + throw new IllegalStateException(message); + } } diff --git a/src/main/java/net/minecraftforge/common/capabilities/CapabilityToken.java b/src/main/java/net/minecraftforge/common/capabilities/CapabilityToken.java index 03bc8ea501..01faf39986 100644 --- a/src/main/java/net/minecraftforge/common/capabilities/CapabilityToken.java +++ b/src/main/java/net/minecraftforge/common/capabilities/CapabilityToken.java @@ -21,16 +21,13 @@ import net.minecraftforge.fml.common.asm.CapabilityTokenSubclass; * } * */ -public abstract class CapabilityToken -{ - protected final String getType() - { +public abstract class CapabilityToken { + protected final String getType() { throw new RuntimeException("This will be implemented by a transformer"); } @Override - public String toString() - { + 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 1afbde8cb6..7ad3c395cb 100644 --- a/src/main/java/net/minecraftforge/common/capabilities/RegisterCapabilitiesEvent.java +++ b/src/main/java/net/minecraftforge/common/capabilities/RegisterCapabilitiesEvent.java @@ -15,19 +15,21 @@ import net.minecraftforge.fml.event.IModBusEvent; /** * This event fires when it is time to register your capabilities. * @see Capability + * + * @deprecated Use {@link AutoRegisterCapability} annotation on your class. */ -public final class RegisterCapabilitiesEvent extends Event implements IModBusEvent -{ +@Deprecated(forRemoval = true, since = "1.21") +public final class RegisterCapabilitiesEvent extends Event implements IModBusEvent { /** * Registers a capability to be consumed by others. * APIs who define the capability should call this. - * To retrieve the Capability instance, use the @CapabilityInject annotation. + * This is meant to allow Capability consumers to have soft dependencies on the Capability type. + * But be automatically notified when the Class actually exists. Meaning it's safe to create their implementations. * - * @param type The type to be registered + * To retrieve the Capability instance, use the {@link CapabilityManager} gets functions. */ - public void register(Class type) - { - Objects.requireNonNull(type,"Attempted to register a capability with invalid type"); - CapabilityManager.INSTANCE.get(Type.getInternalName(type), true); + public void register(Class type) { + Objects.requireNonNull(type, "Attempted to register a capability with invalid type"); + CapabilityManager.get(Type.getInternalName(type), null, true); } } \ No newline at end of file