From 0ff58cdc3e85ef69bbb3a436a1335b5a882d71c3 Mon Sep 17 00:00:00 2001 From: Jonathan Coates Date: Tue, 17 Oct 2023 21:59:16 +0100 Subject: [PATCH] Unify the generic peirpheral system a litte Allows registering arbitrary block lookup functions instead of a platform-specific capability. This is roughly what Fabric did before, but generalised to also take an invalidation callback. This callback is a little nasty - it needs to be a NonNullableConsumer on Forge, but that class isn't available on Fabric. For now, we make the lookup function (and thus the generic peripheral provider) generic on some type, then specialise that on the Forge side. Hopefully we can clean this up when NeoForge reworks capabilities. --- .../peripheral/generic/ComponentLookup.java | 36 ++++++++++ .../generic/GenericPeripheralBuilder.java | 31 +++------ .../generic/GenericPeripheralProvider.java | 69 +++++++++++++++++++ .../computercraft/impl/Peripherals.java | 35 ++++++++++ .../computercraft/shared/ComputerCraft.java | 3 + .../generic/GenericPeripheralProvider.java | 52 -------------- .../shared/platform/PlatformHelperImpl.java | 12 ++-- .../impl/ComputerCraftAPIImpl.java | 3 +- .../computercraft/impl/Peripherals.java | 45 ++++++++++-- .../generic/GenericPeripheralProvider.java | 54 --------------- .../shared/platform/InvalidateCallback.java | 33 +++++++++ .../shared/platform/PlatformHelperImpl.java | 14 ++-- .../shared/util/CapabilityUtil.java | 13 +--- 13 files changed, 244 insertions(+), 156 deletions(-) create mode 100644 projects/common/src/main/java/dan200/computercraft/shared/peripheral/generic/ComponentLookup.java create mode 100644 projects/common/src/main/java/dan200/computercraft/shared/peripheral/generic/GenericPeripheralProvider.java create mode 100644 projects/fabric/src/main/java/dan200/computercraft/impl/Peripherals.java delete mode 100644 projects/fabric/src/main/java/dan200/computercraft/shared/peripheral/generic/GenericPeripheralProvider.java delete mode 100644 projects/forge/src/main/java/dan200/computercraft/shared/peripheral/generic/GenericPeripheralProvider.java create mode 100644 projects/forge/src/main/java/dan200/computercraft/shared/platform/InvalidateCallback.java diff --git a/projects/common/src/main/java/dan200/computercraft/shared/peripheral/generic/ComponentLookup.java b/projects/common/src/main/java/dan200/computercraft/shared/peripheral/generic/ComponentLookup.java new file mode 100644 index 000000000..f117e7ee3 --- /dev/null +++ b/projects/common/src/main/java/dan200/computercraft/shared/peripheral/generic/ComponentLookup.java @@ -0,0 +1,36 @@ +// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers +// +// SPDX-License-Identifier: MPL-2.0 + +package dan200.computercraft.shared.peripheral.generic; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; + +import javax.annotation.Nullable; + +/** + * Extract some component (for instance a capability on Forge, or a {@code BlockApiLookup} on Fabric) from a block and + * block entity. + * + * @param A platform-specific type, used for the invalidation callback. + */ +public interface ComponentLookup { + /** + * Extract some component from a block in the world. + * + * @param level The current level. + * @param pos The position of the block in the level. + * @param state The block state at that position. + * @param blockEntity The block entity at that position. + * @param side The side of the block to extract the component from. Implementations should try to use a + * sideless lookup first, but may fall back to a sided lookup if needed. + * @param invalidate An invalidation function to call if this component changes. + * @return The found component, or {@code null} if not present. + */ + @Nullable + Object find(Level level, BlockPos pos, BlockState state, BlockEntity blockEntity, Direction side, C invalidate); +} diff --git a/projects/common/src/main/java/dan200/computercraft/shared/peripheral/generic/GenericPeripheralBuilder.java b/projects/common/src/main/java/dan200/computercraft/shared/peripheral/generic/GenericPeripheralBuilder.java index e6b69f0cb..fc2457905 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/peripheral/generic/GenericPeripheralBuilder.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/peripheral/generic/GenericPeripheralBuilder.java @@ -6,12 +6,9 @@ package dan200.computercraft.shared.peripheral.generic; import dan200.computercraft.api.peripheral.IPeripheral; import dan200.computercraft.api.peripheral.PeripheralType; -import dan200.computercraft.core.methods.MethodSupplier; import dan200.computercraft.core.methods.NamedMethod; import dan200.computercraft.core.methods.PeripheralMethod; -import dan200.computercraft.shared.computer.core.ServerContext; import net.minecraft.core.Direction; -import net.minecraft.server.MinecraftServer; import net.minecraft.world.level.block.entity.BlockEntity; import javax.annotation.Nullable; @@ -28,16 +25,10 @@ import java.util.Set; * See the platform-specific peripheral providers for the usage of this. */ final class GenericPeripheralBuilder { - private final MethodSupplier peripheralMethods; - private @Nullable String name; private final Set additionalTypes = new HashSet<>(0); private final ArrayList methods = new ArrayList<>(); - GenericPeripheralBuilder(MinecraftServer server) { - peripheralMethods = ServerContext.get(server).peripheralMethods(); - } - @Nullable IPeripheral toPeripheral(BlockEntity blockEntity, Direction side) { if (methods.isEmpty()) return null; @@ -46,18 +37,16 @@ final class GenericPeripheralBuilder { return new GenericPeripheral(blockEntity, side, name, additionalTypes, methods); } - boolean addMethods(Object target) { - return peripheralMethods.forEachSelfMethod(target, (name, method, info) -> { - methods.add(new SaturatedMethod(target, name, method)); + void addMethod(Object target, String name, PeripheralMethod method, @Nullable NamedMethod info) { + methods.add(new SaturatedMethod(target, name, method)); - // If we have a peripheral type, use it. Always pick the smallest one, so it's consistent (assuming mods - // don't change). - var type = info == null ? null : info.genericType(); - if (type != null && type.getPrimaryType() != null) { - var primaryType = type.getPrimaryType(); - if (this.name == null || this.name.compareTo(primaryType) > 0) this.name = primaryType; - } - if (type != null) additionalTypes.addAll(type.getAdditionalTypes()); - }); + // If we have a peripheral type, use it. Always pick the smallest one, so it's consistent (assuming mods + // don't change). + var type = info == null ? null : info.genericType(); + if (type != null && type.getPrimaryType() != null) { + var primaryType = type.getPrimaryType(); + if (this.name == null || this.name.compareTo(primaryType) > 0) this.name = primaryType; + } + if (type != null) additionalTypes.addAll(type.getAdditionalTypes()); } } diff --git a/projects/common/src/main/java/dan200/computercraft/shared/peripheral/generic/GenericPeripheralProvider.java b/projects/common/src/main/java/dan200/computercraft/shared/peripheral/generic/GenericPeripheralProvider.java new file mode 100644 index 000000000..769085826 --- /dev/null +++ b/projects/common/src/main/java/dan200/computercraft/shared/peripheral/generic/GenericPeripheralProvider.java @@ -0,0 +1,69 @@ +// SPDX-FileCopyrightText: 2020 The CC: Tweaked Developers +// +// SPDX-License-Identifier: MPL-2.0 + +package dan200.computercraft.shared.peripheral.generic; + +import dan200.computercraft.api.lua.GenericSource; +import dan200.computercraft.api.peripheral.IPeripheral; +import dan200.computercraft.core.methods.MethodSupplier; +import dan200.computercraft.core.methods.PeripheralMethod; +import dan200.computercraft.shared.computer.core.ServerContext; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.entity.BlockEntity; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * A peripheral provider which finds methods from various {@linkplain GenericSource generic sources}. + *

+ * Methods are found using the original block entity itself and a registered list of {@link ComponentLookup}s. + * + * @param A platform-specific type, used for the invalidation callback. + */ +public final class GenericPeripheralProvider { + private static final Logger LOG = LoggerFactory.getLogger(GenericPeripheralProvider.class); + + private final List> lookups = new ArrayList<>(); + + /** + * Register a component lookup function. + * + * @param lookup The component lookup function. + */ + public synchronized void registerLookup(ComponentLookup lookup) { + Objects.requireNonNull(lookup); + if (!lookups.contains(lookup)) lookups.add(lookup); + } + + public void forEachMethod(MethodSupplier methods, Level level, BlockPos pos, Direction side, BlockEntity blockEntity, C invalidate, MethodSupplier.TargetedConsumer consumer) { + methods.forEachMethod(blockEntity, consumer); + + for (var lookup : lookups) { + var contents = lookup.find(level, pos, blockEntity.getBlockState(), blockEntity, side, invalidate); + if (contents != null) methods.forEachMethod(contents, consumer); + } + } + + @Nullable + public IPeripheral getPeripheral(Level level, BlockPos pos, Direction side, @Nullable BlockEntity blockEntity, C invalidate) { + if (blockEntity == null) return null; + + var server = level.getServer(); + if (server == null) { + LOG.warn("Fetching peripherals on a non-server level {}.", level, new IllegalStateException("Fetching peripherals on a non-server level.")); + return null; + } + + var builder = new GenericPeripheralBuilder(); + forEachMethod(ServerContext.get(server).peripheralMethods(), level, pos, side, blockEntity, invalidate, builder::addMethod); + return builder.toPeripheral(blockEntity, side); + } +} diff --git a/projects/fabric/src/main/java/dan200/computercraft/impl/Peripherals.java b/projects/fabric/src/main/java/dan200/computercraft/impl/Peripherals.java new file mode 100644 index 000000000..c13a7c8d2 --- /dev/null +++ b/projects/fabric/src/main/java/dan200/computercraft/impl/Peripherals.java @@ -0,0 +1,35 @@ +// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers +// +// SPDX-License-Identifier: MPL-2.0 + +package dan200.computercraft.impl; + +import dan200.computercraft.api.peripheral.IPeripheral; +import dan200.computercraft.shared.peripheral.generic.ComponentLookup; +import dan200.computercraft.shared.peripheral.generic.GenericPeripheralProvider; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.entity.BlockEntity; + +import javax.annotation.Nullable; + +/** + * The registry for peripheral providers. + *

+ * This lives in the {@code impl} package despite it not being part of the public API, in order to mirror Forge's class. + */ +public final class Peripherals { + private static final GenericPeripheralProvider genericProvider = new GenericPeripheralProvider<>(); + + private Peripherals() { + } + + public static void addGenericLookup(ComponentLookup lookup) { + genericProvider.registerLookup(lookup); + } + + public static @Nullable IPeripheral getGenericPeripheral(Level level, BlockPos pos, Direction side, @Nullable BlockEntity blockEntity, Runnable invalidate) { + return genericProvider.getPeripheral(level, pos, side, blockEntity, invalidate); + } +} diff --git a/projects/fabric/src/main/java/dan200/computercraft/shared/ComputerCraft.java b/projects/fabric/src/main/java/dan200/computercraft/shared/ComputerCraft.java index e950d998b..d187a1c0e 100644 --- a/projects/fabric/src/main/java/dan200/computercraft/shared/ComputerCraft.java +++ b/projects/fabric/src/main/java/dan200/computercraft/shared/ComputerCraft.java @@ -8,6 +8,7 @@ import dan200.computercraft.api.ComputerCraftAPI; import dan200.computercraft.api.detail.FabricDetailRegistries; import dan200.computercraft.api.node.wired.WiredElementLookup; import dan200.computercraft.api.peripheral.PeripheralLookup; +import dan200.computercraft.impl.Peripherals; import dan200.computercraft.shared.command.CommandComputerCraft; import dan200.computercraft.shared.config.Config; import dan200.computercraft.shared.config.ConfigSpec; @@ -100,6 +101,8 @@ public class ComputerCraft { FabricDetailRegistries.FLUID_VARIANT.addProvider(FluidDetails::fill); ComputerCraftAPI.registerGenericSource(new InventoryMethods()); + + Peripherals.addGenericLookup((world, pos, state, blockEntity, side, invalidate) -> InventoryMethods.extractContainer(world, pos, state, blockEntity, side)); } private record ReloadListener(String name, PreparableReloadListener listener) diff --git a/projects/fabric/src/main/java/dan200/computercraft/shared/peripheral/generic/GenericPeripheralProvider.java b/projects/fabric/src/main/java/dan200/computercraft/shared/peripheral/generic/GenericPeripheralProvider.java deleted file mode 100644 index ac0d54e92..000000000 --- a/projects/fabric/src/main/java/dan200/computercraft/shared/peripheral/generic/GenericPeripheralProvider.java +++ /dev/null @@ -1,52 +0,0 @@ -// SPDX-FileCopyrightText: 2020 The CC: Tweaked Developers -// -// SPDX-License-Identifier: MPL-2.0 - -package dan200.computercraft.shared.peripheral.generic; - -import dan200.computercraft.api.peripheral.IPeripheral; -import dan200.computercraft.shared.peripheral.generic.methods.InventoryMethods; -import net.minecraft.core.BlockPos; -import net.minecraft.core.Direction; -import net.minecraft.world.level.Level; -import net.minecraft.world.level.block.entity.BlockEntity; -import net.minecraft.world.level.block.state.BlockState; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.annotation.Nullable; -import java.util.List; - -public class GenericPeripheralProvider { - private static final Logger LOG = LoggerFactory.getLogger(GenericPeripheralProvider.class); - - interface Lookup { - @Nullable - T find(Level world, BlockPos pos, BlockState state, @Nullable BlockEntity blockEntity, Direction context); - } - - private static final List> lookups = List.of( - InventoryMethods::extractContainer - ); - - @Nullable - public static IPeripheral getPeripheral(Level level, BlockPos pos, Direction side, @Nullable BlockEntity blockEntity) { - if (blockEntity == null) return null; - - var server = level.getServer(); - if (server == null) { - LOG.warn("Fetching peripherals on a non-server level {}.", level, new IllegalStateException("Fetching peripherals on a non-server level.")); - return null; - } - - var builder = new GenericPeripheralBuilder(server); - builder.addMethods(blockEntity); - - for (var lookup : lookups) { - var contents = lookup.find(level, pos, blockEntity.getBlockState(), blockEntity, side); - if (contents != null) builder.addMethods(contents); - } - - return builder.toPeripheral(blockEntity, side); - } -} diff --git a/projects/fabric/src/main/java/dan200/computercraft/shared/platform/PlatformHelperImpl.java b/projects/fabric/src/main/java/dan200/computercraft/shared/platform/PlatformHelperImpl.java index dc6931b8e..9c56aa054 100644 --- a/projects/fabric/src/main/java/dan200/computercraft/shared/platform/PlatformHelperImpl.java +++ b/projects/fabric/src/main/java/dan200/computercraft/shared/platform/PlatformHelperImpl.java @@ -14,12 +14,12 @@ import dan200.computercraft.api.network.wired.WiredElement; import dan200.computercraft.api.node.wired.WiredElementLookup; import dan200.computercraft.api.peripheral.IPeripheral; import dan200.computercraft.api.peripheral.PeripheralLookup; +import dan200.computercraft.impl.Peripherals; import dan200.computercraft.mixin.ArgumentTypeInfosAccessor; import dan200.computercraft.shared.config.ConfigFile; import dan200.computercraft.shared.network.NetworkMessage; import dan200.computercraft.shared.network.client.ClientNetworkContext; import dan200.computercraft.shared.network.container.ContainerData; -import dan200.computercraft.shared.peripheral.generic.GenericPeripheralProvider; import dan200.computercraft.shared.util.InventoryUtil; import net.fabricmc.fabric.api.event.player.AttackEntityCallback; import net.fabricmc.fabric.api.event.player.UseBlockCallback; @@ -202,7 +202,7 @@ public class PlatformHelperImpl implements PlatformHelper { @Override public ComponentAccess createPeripheralAccess(Consumer invalidate) { - return new PeripheralAccessImpl(); + return new PeripheralAccessImpl(invalidate); } @Override @@ -476,8 +476,11 @@ public class PlatformHelperImpl implements PlatformHelper { } private static final class PeripheralAccessImpl extends ComponentAccessImpl { - private PeripheralAccessImpl() { + private final Runnable[] invalidators = new Runnable[6]; + + private PeripheralAccessImpl(Consumer invalidate) { super(PeripheralLookup.get()); + for (var dir : Direction.values()) invalidators[dir.ordinal()] = () -> invalidate.accept(dir); } @Nullable @@ -487,7 +490,8 @@ public class PlatformHelperImpl implements PlatformHelper { if (result != null) return result; var cache = caches[direction.ordinal()]; - return GenericPeripheralProvider.getPeripheral(level, cache.getPos(), direction.getOpposite(), cache.getBlockEntity()); + var invalidate = invalidators[direction.ordinal()]; + return Peripherals.getGenericPeripheral(level, cache.getPos(), direction.getOpposite(), cache.getBlockEntity(), invalidate); } } } diff --git a/projects/forge/src/main/java/dan200/computercraft/impl/ComputerCraftAPIImpl.java b/projects/forge/src/main/java/dan200/computercraft/impl/ComputerCraftAPIImpl.java index 14d7169be..597a71618 100644 --- a/projects/forge/src/main/java/dan200/computercraft/impl/ComputerCraftAPIImpl.java +++ b/projects/forge/src/main/java/dan200/computercraft/impl/ComputerCraftAPIImpl.java @@ -11,7 +11,6 @@ import dan200.computercraft.api.network.wired.WiredElement; import dan200.computercraft.api.peripheral.IPeripheralProvider; import dan200.computercraft.impl.detail.DetailRegistryImpl; import dan200.computercraft.shared.details.FluidData; -import dan200.computercraft.shared.peripheral.generic.GenericPeripheralProvider; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.world.level.BlockGetter; @@ -45,7 +44,7 @@ public final class ComputerCraftAPIImpl extends AbstractComputerCraftAPI impleme @Override public void registerGenericCapability(Capability capability) { - GenericPeripheralProvider.addCapability(capability); + Peripherals.registerGenericCapability(capability); } @Override diff --git a/projects/forge/src/main/java/dan200/computercraft/impl/Peripherals.java b/projects/forge/src/main/java/dan200/computercraft/impl/Peripherals.java index a39fa1778..8e47d3109 100644 --- a/projects/forge/src/main/java/dan200/computercraft/impl/Peripherals.java +++ b/projects/forge/src/main/java/dan200/computercraft/impl/Peripherals.java @@ -6,12 +6,16 @@ package dan200.computercraft.impl; import dan200.computercraft.api.peripheral.IPeripheral; import dan200.computercraft.api.peripheral.IPeripheralProvider; +import dan200.computercraft.shared.peripheral.generic.ComponentLookup; import dan200.computercraft.shared.peripheral.generic.GenericPeripheralProvider; +import dan200.computercraft.shared.platform.InvalidateCallback; import dan200.computercraft.shared.util.CapabilityUtil; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.world.level.Level; -import net.minecraftforge.common.util.NonNullConsumer; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraftforge.common.capabilities.Capability; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -22,10 +26,16 @@ import java.util.Objects; import static dan200.computercraft.shared.Capabilities.CAPABILITY_PERIPHERAL; +/** + * The registry for peripheral providers. + *

+ * This lives in the {@code impl} package despite it not being part of the public API, in order to mirror Forge's class. + */ public final class Peripherals { private static final Logger LOG = LoggerFactory.getLogger(Peripherals.class); private static final Collection providers = new LinkedHashSet<>(); + private static final GenericPeripheralProvider genericProvider = new GenericPeripheralProvider<>(); private Peripherals() { } @@ -35,13 +45,39 @@ public final class Peripherals { providers.add(provider); } + public static void registerGenericLookup(ComponentLookup lookup) { + genericProvider.registerLookup(lookup); + } + + /** + * A {@link ComponentLookup} for {@linkplain Capability capabilities}. + *

+ * This is a record to ensure that adding the same capability multiple times only results in one lookup being + * present in the resulting list. + * + * @param capability The capability to lookup + * @param The type of the capability we look up. + */ + private record CapabilityLookup(Capability capability) implements ComponentLookup { + @Nullable + @Override + public T find(Level level, BlockPos pos, BlockState state, BlockEntity blockEntity, Direction side, InvalidateCallback invalidate) { + return CapabilityUtil.unwrap(CapabilityUtil.getCapability(blockEntity, this.capability(), side), invalidate); + } + } + + public static void registerGenericCapability(Capability capability) { + Objects.requireNonNull(capability, "Capability cannot be null"); + registerGenericLookup(new CapabilityLookup<>(capability)); + } + @Nullable - public static IPeripheral getPeripheral(Level world, BlockPos pos, Direction side, NonNullConsumer invalidate) { + public static IPeripheral getPeripheral(Level world, BlockPos pos, Direction side, InvalidateCallback invalidate) { return world.isInWorldBounds(pos) && !world.isClientSide ? getPeripheralAt(world, pos, side, invalidate) : null; } @Nullable - private static IPeripheral getPeripheralAt(Level world, BlockPos pos, Direction side, NonNullConsumer invalidate) { + private static IPeripheral getPeripheralAt(Level world, BlockPos pos, Direction side, InvalidateCallback invalidate) { var block = world.getBlockEntity(pos); if (block != null) { var peripheral = block.getCapability(CAPABILITY_PERIPHERAL, side); @@ -58,7 +94,6 @@ public final class Peripherals { } } - return GenericPeripheralProvider.getPeripheral(world, pos, side, invalidate); + return genericProvider.getPeripheral(world, pos, side, block, invalidate); } - } diff --git a/projects/forge/src/main/java/dan200/computercraft/shared/peripheral/generic/GenericPeripheralProvider.java b/projects/forge/src/main/java/dan200/computercraft/shared/peripheral/generic/GenericPeripheralProvider.java deleted file mode 100644 index 64c9333e1..000000000 --- a/projects/forge/src/main/java/dan200/computercraft/shared/peripheral/generic/GenericPeripheralProvider.java +++ /dev/null @@ -1,54 +0,0 @@ -// SPDX-FileCopyrightText: 2020 The CC: Tweaked Developers -// -// SPDX-License-Identifier: MPL-2.0 - -package dan200.computercraft.shared.peripheral.generic; - -import dan200.computercraft.api.peripheral.IPeripheral; -import dan200.computercraft.shared.util.CapabilityUtil; -import net.minecraft.core.BlockPos; -import net.minecraft.core.Direction; -import net.minecraft.world.level.Level; -import net.minecraftforge.common.capabilities.Capability; -import net.minecraftforge.common.util.NonNullConsumer; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.annotation.Nullable; -import java.util.ArrayList; -import java.util.Objects; - -public class GenericPeripheralProvider { - private static final Logger LOG = LoggerFactory.getLogger(GenericPeripheralProvider.class); - - private static final ArrayList> capabilities = new ArrayList<>(); - - public static synchronized void addCapability(Capability capability) { - Objects.requireNonNull(capability, "Capability cannot be null"); - if (!capabilities.contains(capability)) capabilities.add(capability); - } - - @Nullable - public static IPeripheral getPeripheral(Level level, BlockPos pos, Direction side, NonNullConsumer invalidate) { - var blockEntity = level.getBlockEntity(pos); - if (blockEntity == null) return null; - - var server = level.getServer(); - if (server == null) { - LOG.warn("Fetching peripherals on a non-server level {}.", level, new IllegalStateException("Fetching peripherals on a non-server level.")); - return null; - } - - var builder = new GenericPeripheralBuilder(server); - builder.addMethods(blockEntity); - - for (var capability : capabilities) { - var wrapper = CapabilityUtil.getCapability(blockEntity, capability, side); - wrapper.ifPresent(contents -> { - if (builder.addMethods(contents)) CapabilityUtil.addListener(wrapper, invalidate); - }); - } - - return builder.toPeripheral(blockEntity, side); - } -} diff --git a/projects/forge/src/main/java/dan200/computercraft/shared/platform/InvalidateCallback.java b/projects/forge/src/main/java/dan200/computercraft/shared/platform/InvalidateCallback.java new file mode 100644 index 000000000..441f44b14 --- /dev/null +++ b/projects/forge/src/main/java/dan200/computercraft/shared/platform/InvalidateCallback.java @@ -0,0 +1,33 @@ +// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers +// +// SPDX-License-Identifier: MPL-2.0 + +package dan200.computercraft.shared.platform; + +import dan200.computercraft.shared.peripheral.generic.ComponentLookup; +import net.minecraftforge.common.util.LazyOptional; +import net.minecraftforge.common.util.NonNullConsumer; + +/** + * A function which may be called when a capability (or some other object) has been invalidated. + *

+ * This extends {@link NonNullConsumer} for use with {@link LazyOptional#addListener(NonNullConsumer)}, and + * {@link Runnable} for use with {@link ComponentLookup}. + */ +public interface InvalidateCallback extends Runnable, NonNullConsumer { + @Override + default void accept(Object o) { + run(); + } + + /** + * Cast this callback to a {@link NonNullConsumer} of an arbitrary type. + * + * @param The type of the consumer, normally a {@link LazyOptional}. + * @return {@code this}, but with a compatible type. + */ + @SuppressWarnings("unchecked") + default NonNullConsumer castConsumer() { + return (NonNullConsumer) this; + } +} diff --git a/projects/forge/src/main/java/dan200/computercraft/shared/platform/PlatformHelperImpl.java b/projects/forge/src/main/java/dan200/computercraft/shared/platform/PlatformHelperImpl.java index 3e47d4b16..5d2152b7b 100644 --- a/projects/forge/src/main/java/dan200/computercraft/shared/platform/PlatformHelperImpl.java +++ b/projects/forge/src/main/java/dan200/computercraft/shared/platform/PlatformHelperImpl.java @@ -63,7 +63,6 @@ import net.minecraftforge.common.crafting.CraftingHelper; import net.minecraftforge.common.crafting.conditions.ICondition; import net.minecraftforge.common.crafting.conditions.ModLoadedCondition; import net.minecraftforge.common.extensions.IForgeMenuType; -import net.minecraftforge.common.util.NonNullConsumer; import net.minecraftforge.event.ForgeEventFactory; import net.minecraftforge.eventbus.api.Event; import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; @@ -429,20 +428,19 @@ public class PlatformHelperImpl implements PlatformHelper { } private abstract static class ComponentAccessImpl implements ComponentAccess { - private final NonNullConsumer[] invalidators; + private final InvalidateCallback[] invalidators; private @Nullable Level level; private @Nullable BlockPos pos; ComponentAccessImpl(Consumer invalidate) { // Generate a cache of invalidation functions so we can guarantee we only ever have one registered per // capability - there's no way to remove these callbacks! - @SuppressWarnings({ "unchecked", "rawtypes" }) - var invalidators = this.invalidators = new NonNullConsumer[6]; - for (var dir : Direction.values()) invalidators[dir.ordinal()] = x -> invalidate.accept(dir); + var invalidators = this.invalidators = new InvalidateCallback[6]; + for (var dir : Direction.values()) invalidators[dir.ordinal()] = () -> invalidate.accept(dir); } @Nullable - protected abstract T get(ServerLevel world, BlockPos pos, Direction side, NonNullConsumer invalidate); + protected abstract T get(ServerLevel world, BlockPos pos, Direction side, InvalidateCallback invalidate); @Nullable @Override @@ -463,7 +461,7 @@ public class PlatformHelperImpl implements PlatformHelper { @Nullable @Override - protected IPeripheral get(ServerLevel world, BlockPos pos, Direction side, NonNullConsumer invalidate) { + protected IPeripheral get(ServerLevel world, BlockPos pos, Direction side, InvalidateCallback invalidate) { return Peripherals.getPeripheral(world, pos, side, invalidate); } } @@ -478,7 +476,7 @@ public class PlatformHelperImpl implements PlatformHelper { @Nullable @Override - protected T get(ServerLevel world, BlockPos pos, Direction side, NonNullConsumer invalidate) { + protected T get(ServerLevel world, BlockPos pos, Direction side, InvalidateCallback invalidate) { if (!world.isLoaded(pos)) return null; var blockEntity = world.getBlockEntity(pos); diff --git a/projects/forge/src/main/java/dan200/computercraft/shared/util/CapabilityUtil.java b/projects/forge/src/main/java/dan200/computercraft/shared/util/CapabilityUtil.java index 9c36a74e8..c1e1dddf8 100644 --- a/projects/forge/src/main/java/dan200/computercraft/shared/util/CapabilityUtil.java +++ b/projects/forge/src/main/java/dan200/computercraft/shared/util/CapabilityUtil.java @@ -4,11 +4,11 @@ package dan200.computercraft.shared.util; +import dan200.computercraft.shared.platform.InvalidateCallback; import net.minecraft.core.Direction; import net.minecraftforge.common.capabilities.Capability; import net.minecraftforge.common.capabilities.ICapabilityProvider; import net.minecraftforge.common.util.LazyOptional; -import net.minecraftforge.common.util.NonNullConsumer; import javax.annotation.Nullable; @@ -32,18 +32,11 @@ public final class CapabilityUtil { } } - public static void addListener(LazyOptional p, NonNullConsumer> invalidate) { - // We can make this safe with invalidate::accept, but then we're allocating it's just kind of absurd. - @SuppressWarnings("unchecked") - var safeInvalidate = (NonNullConsumer>) invalidate; - p.addListener(safeInvalidate); - } - @Nullable - public static T unwrap(LazyOptional p, NonNullConsumer> invalidate) { + public static T unwrap(LazyOptional p, InvalidateCallback invalidate) { if (!p.isPresent()) return null; - addListener(p, invalidate); + p.addListener(invalidate.castConsumer()); return p.orElseThrow(NullPointerException::new); }