mirror of
				https://github.com/SquidDev-CC/CC-Tweaked
				synced 2025-10-30 21:23:00 +00:00 
			
		
		
		
	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 <T extends Runnable> type, then specialise that on the Forge side. Hopefully we can clean this up when NeoForge reworks capabilities.
This commit is contained in:
		| @@ -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 <C> A platform-specific type, used for the invalidation callback. | ||||||
|  |  */ | ||||||
|  | public interface ComponentLookup<C extends Runnable> { | ||||||
|  |     /** | ||||||
|  |      * 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); | ||||||
|  | } | ||||||
| @@ -6,12 +6,9 @@ package dan200.computercraft.shared.peripheral.generic; | |||||||
| 
 | 
 | ||||||
| import dan200.computercraft.api.peripheral.IPeripheral; | import dan200.computercraft.api.peripheral.IPeripheral; | ||||||
| import dan200.computercraft.api.peripheral.PeripheralType; | import dan200.computercraft.api.peripheral.PeripheralType; | ||||||
| import dan200.computercraft.core.methods.MethodSupplier; |  | ||||||
| import dan200.computercraft.core.methods.NamedMethod; | import dan200.computercraft.core.methods.NamedMethod; | ||||||
| import dan200.computercraft.core.methods.PeripheralMethod; | import dan200.computercraft.core.methods.PeripheralMethod; | ||||||
| import dan200.computercraft.shared.computer.core.ServerContext; |  | ||||||
| import net.minecraft.core.Direction; | import net.minecraft.core.Direction; | ||||||
| import net.minecraft.server.MinecraftServer; |  | ||||||
| import net.minecraft.world.level.block.entity.BlockEntity; | import net.minecraft.world.level.block.entity.BlockEntity; | ||||||
| 
 | 
 | ||||||
| import javax.annotation.Nullable; | import javax.annotation.Nullable; | ||||||
| @@ -28,16 +25,10 @@ import java.util.Set; | |||||||
|  * See the platform-specific peripheral providers for the usage of this. |  * See the platform-specific peripheral providers for the usage of this. | ||||||
|  */ |  */ | ||||||
| final class GenericPeripheralBuilder { | final class GenericPeripheralBuilder { | ||||||
|     private final MethodSupplier<PeripheralMethod> peripheralMethods; |  | ||||||
| 
 |  | ||||||
|     private @Nullable String name; |     private @Nullable String name; | ||||||
|     private final Set<String> additionalTypes = new HashSet<>(0); |     private final Set<String> additionalTypes = new HashSet<>(0); | ||||||
|     private final ArrayList<SaturatedMethod> methods = new ArrayList<>(); |     private final ArrayList<SaturatedMethod> methods = new ArrayList<>(); | ||||||
| 
 | 
 | ||||||
|     GenericPeripheralBuilder(MinecraftServer server) { |  | ||||||
|         peripheralMethods = ServerContext.get(server).peripheralMethods(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Nullable |     @Nullable | ||||||
|     IPeripheral toPeripheral(BlockEntity blockEntity, Direction side) { |     IPeripheral toPeripheral(BlockEntity blockEntity, Direction side) { | ||||||
|         if (methods.isEmpty()) return null; |         if (methods.isEmpty()) return null; | ||||||
| @@ -46,18 +37,16 @@ final class GenericPeripheralBuilder { | |||||||
|         return new GenericPeripheral(blockEntity, side, name, additionalTypes, methods); |         return new GenericPeripheral(blockEntity, side, name, additionalTypes, methods); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     boolean addMethods(Object target) { |     void addMethod(Object target, String name, PeripheralMethod method, @Nullable NamedMethod<PeripheralMethod> info) { | ||||||
|         return peripheralMethods.forEachSelfMethod(target, (name, method, info) -> { |         methods.add(new SaturatedMethod(target, name, method)); | ||||||
|             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 |         // If we have a peripheral type, use it. Always pick the smallest one, so it's consistent (assuming mods | ||||||
|             // don't change). |         // don't change). | ||||||
|             var type = info == null ? null : info.genericType(); |         var type = info == null ? null : info.genericType(); | ||||||
|             if (type != null && type.getPrimaryType() != null) { |         if (type != null && type.getPrimaryType() != null) { | ||||||
|                 var primaryType = type.getPrimaryType(); |             var primaryType = type.getPrimaryType(); | ||||||
|                 if (this.name == null || this.name.compareTo(primaryType) > 0) this.name = primaryType; |             if (this.name == null || this.name.compareTo(primaryType) > 0) this.name = primaryType; | ||||||
|             } |         } | ||||||
|             if (type != null) additionalTypes.addAll(type.getAdditionalTypes()); |         if (type != null) additionalTypes.addAll(type.getAdditionalTypes()); | ||||||
|         }); |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -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}. | ||||||
|  |  * <p> | ||||||
|  |  * Methods are found using the original block entity itself and a registered list of {@link ComponentLookup}s. | ||||||
|  |  * | ||||||
|  |  * @param <C> A platform-specific type, used for the invalidation callback. | ||||||
|  |  */ | ||||||
|  | public final class GenericPeripheralProvider<C extends Runnable> { | ||||||
|  |     private static final Logger LOG = LoggerFactory.getLogger(GenericPeripheralProvider.class); | ||||||
|  | 
 | ||||||
|  |     private final List<ComponentLookup<? super C>> lookups = new ArrayList<>(); | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Register a component lookup function. | ||||||
|  |      * | ||||||
|  |      * @param lookup The component lookup function. | ||||||
|  |      */ | ||||||
|  |     public synchronized void registerLookup(ComponentLookup<? super C> lookup) { | ||||||
|  |         Objects.requireNonNull(lookup); | ||||||
|  |         if (!lookups.contains(lookup)) lookups.add(lookup); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public void forEachMethod(MethodSupplier<PeripheralMethod> methods, Level level, BlockPos pos, Direction side, BlockEntity blockEntity, C invalidate, MethodSupplier.TargetedConsumer<PeripheralMethod> 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); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -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. | ||||||
|  |  * <p> | ||||||
|  |  * 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<Runnable> genericProvider = new GenericPeripheralProvider<>(); | ||||||
|  | 
 | ||||||
|  |     private Peripherals() { | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public static void addGenericLookup(ComponentLookup<? super Runnable> 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); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -8,6 +8,7 @@ import dan200.computercraft.api.ComputerCraftAPI; | |||||||
| import dan200.computercraft.api.detail.FabricDetailRegistries; | import dan200.computercraft.api.detail.FabricDetailRegistries; | ||||||
| import dan200.computercraft.api.node.wired.WiredElementLookup; | import dan200.computercraft.api.node.wired.WiredElementLookup; | ||||||
| import dan200.computercraft.api.peripheral.PeripheralLookup; | import dan200.computercraft.api.peripheral.PeripheralLookup; | ||||||
|  | import dan200.computercraft.impl.Peripherals; | ||||||
| import dan200.computercraft.shared.command.CommandComputerCraft; | import dan200.computercraft.shared.command.CommandComputerCraft; | ||||||
| import dan200.computercraft.shared.config.Config; | import dan200.computercraft.shared.config.Config; | ||||||
| import dan200.computercraft.shared.config.ConfigSpec; | import dan200.computercraft.shared.config.ConfigSpec; | ||||||
| @@ -100,6 +101,8 @@ public class ComputerCraft { | |||||||
|         FabricDetailRegistries.FLUID_VARIANT.addProvider(FluidDetails::fill); |         FabricDetailRegistries.FLUID_VARIANT.addProvider(FluidDetails::fill); | ||||||
| 
 | 
 | ||||||
|         ComputerCraftAPI.registerGenericSource(new InventoryMethods()); |         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) |     private record ReloadListener(String name, PreparableReloadListener listener) | ||||||
|   | |||||||
| @@ -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<T> { |  | ||||||
|         @Nullable |  | ||||||
|         T find(Level world, BlockPos pos, BlockState state, @Nullable BlockEntity blockEntity, Direction context); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     private static final List<Lookup<?>> 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); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -14,12 +14,12 @@ import dan200.computercraft.api.network.wired.WiredElement; | |||||||
| import dan200.computercraft.api.node.wired.WiredElementLookup; | import dan200.computercraft.api.node.wired.WiredElementLookup; | ||||||
| import dan200.computercraft.api.peripheral.IPeripheral; | import dan200.computercraft.api.peripheral.IPeripheral; | ||||||
| import dan200.computercraft.api.peripheral.PeripheralLookup; | import dan200.computercraft.api.peripheral.PeripheralLookup; | ||||||
|  | import dan200.computercraft.impl.Peripherals; | ||||||
| import dan200.computercraft.mixin.ArgumentTypeInfosAccessor; | import dan200.computercraft.mixin.ArgumentTypeInfosAccessor; | ||||||
| import dan200.computercraft.shared.config.ConfigFile; | import dan200.computercraft.shared.config.ConfigFile; | ||||||
| import dan200.computercraft.shared.network.NetworkMessage; | import dan200.computercraft.shared.network.NetworkMessage; | ||||||
| import dan200.computercraft.shared.network.client.ClientNetworkContext; | import dan200.computercraft.shared.network.client.ClientNetworkContext; | ||||||
| import dan200.computercraft.shared.network.container.ContainerData; | import dan200.computercraft.shared.network.container.ContainerData; | ||||||
| import dan200.computercraft.shared.peripheral.generic.GenericPeripheralProvider; |  | ||||||
| import dan200.computercraft.shared.util.InventoryUtil; | import dan200.computercraft.shared.util.InventoryUtil; | ||||||
| import net.fabricmc.fabric.api.event.player.AttackEntityCallback; | import net.fabricmc.fabric.api.event.player.AttackEntityCallback; | ||||||
| import net.fabricmc.fabric.api.event.player.UseBlockCallback; | import net.fabricmc.fabric.api.event.player.UseBlockCallback; | ||||||
| @@ -202,7 +202,7 @@ public class PlatformHelperImpl implements PlatformHelper { | |||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|     public ComponentAccess<IPeripheral> createPeripheralAccess(Consumer<Direction> invalidate) { |     public ComponentAccess<IPeripheral> createPeripheralAccess(Consumer<Direction> invalidate) { | ||||||
|         return new PeripheralAccessImpl(); |         return new PeripheralAccessImpl(invalidate); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
| @@ -476,8 +476,11 @@ public class PlatformHelperImpl implements PlatformHelper { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private static final class PeripheralAccessImpl extends ComponentAccessImpl<IPeripheral> { |     private static final class PeripheralAccessImpl extends ComponentAccessImpl<IPeripheral> { | ||||||
|         private PeripheralAccessImpl() { |         private final Runnable[] invalidators = new Runnable[6]; | ||||||
|  | 
 | ||||||
|  |         private PeripheralAccessImpl(Consumer<Direction> invalidate) { | ||||||
|             super(PeripheralLookup.get()); |             super(PeripheralLookup.get()); | ||||||
|  |             for (var dir : Direction.values()) invalidators[dir.ordinal()] = () -> invalidate.accept(dir); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         @Nullable |         @Nullable | ||||||
| @@ -487,7 +490,8 @@ public class PlatformHelperImpl implements PlatformHelper { | |||||||
|             if (result != null) return result; |             if (result != null) return result; | ||||||
| 
 | 
 | ||||||
|             var cache = caches[direction.ordinal()]; |             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); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -11,7 +11,6 @@ import dan200.computercraft.api.network.wired.WiredElement; | |||||||
| import dan200.computercraft.api.peripheral.IPeripheralProvider; | import dan200.computercraft.api.peripheral.IPeripheralProvider; | ||||||
| import dan200.computercraft.impl.detail.DetailRegistryImpl; | import dan200.computercraft.impl.detail.DetailRegistryImpl; | ||||||
| import dan200.computercraft.shared.details.FluidData; | import dan200.computercraft.shared.details.FluidData; | ||||||
| import dan200.computercraft.shared.peripheral.generic.GenericPeripheralProvider; |  | ||||||
| import net.minecraft.core.BlockPos; | import net.minecraft.core.BlockPos; | ||||||
| import net.minecraft.core.Direction; | import net.minecraft.core.Direction; | ||||||
| import net.minecraft.world.level.BlockGetter; | import net.minecraft.world.level.BlockGetter; | ||||||
| @@ -45,7 +44,7 @@ public final class ComputerCraftAPIImpl extends AbstractComputerCraftAPI impleme | |||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|     public void registerGenericCapability(Capability<?> capability) { |     public void registerGenericCapability(Capability<?> capability) { | ||||||
|         GenericPeripheralProvider.addCapability(capability); |         Peripherals.registerGenericCapability(capability); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|   | |||||||
| @@ -6,12 +6,16 @@ package dan200.computercraft.impl; | |||||||
| 
 | 
 | ||||||
| import dan200.computercraft.api.peripheral.IPeripheral; | import dan200.computercraft.api.peripheral.IPeripheral; | ||||||
| import dan200.computercraft.api.peripheral.IPeripheralProvider; | import dan200.computercraft.api.peripheral.IPeripheralProvider; | ||||||
|  | import dan200.computercraft.shared.peripheral.generic.ComponentLookup; | ||||||
| import dan200.computercraft.shared.peripheral.generic.GenericPeripheralProvider; | import dan200.computercraft.shared.peripheral.generic.GenericPeripheralProvider; | ||||||
|  | import dan200.computercraft.shared.platform.InvalidateCallback; | ||||||
| import dan200.computercraft.shared.util.CapabilityUtil; | import dan200.computercraft.shared.util.CapabilityUtil; | ||||||
| import net.minecraft.core.BlockPos; | import net.minecraft.core.BlockPos; | ||||||
| import net.minecraft.core.Direction; | import net.minecraft.core.Direction; | ||||||
| import net.minecraft.world.level.Level; | 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.Logger; | ||||||
| import org.slf4j.LoggerFactory; | import org.slf4j.LoggerFactory; | ||||||
| 
 | 
 | ||||||
| @@ -22,10 +26,16 @@ import java.util.Objects; | |||||||
| 
 | 
 | ||||||
| import static dan200.computercraft.shared.Capabilities.CAPABILITY_PERIPHERAL; | import static dan200.computercraft.shared.Capabilities.CAPABILITY_PERIPHERAL; | ||||||
| 
 | 
 | ||||||
|  | /** | ||||||
|  |  * The registry for peripheral providers. | ||||||
|  |  * <p> | ||||||
|  |  * 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 { | public final class Peripherals { | ||||||
|     private static final Logger LOG = LoggerFactory.getLogger(Peripherals.class); |     private static final Logger LOG = LoggerFactory.getLogger(Peripherals.class); | ||||||
| 
 | 
 | ||||||
|     private static final Collection<IPeripheralProvider> providers = new LinkedHashSet<>(); |     private static final Collection<IPeripheralProvider> providers = new LinkedHashSet<>(); | ||||||
|  |     private static final GenericPeripheralProvider<InvalidateCallback> genericProvider = new GenericPeripheralProvider<>(); | ||||||
| 
 | 
 | ||||||
|     private Peripherals() { |     private Peripherals() { | ||||||
|     } |     } | ||||||
| @@ -35,13 +45,39 @@ public final class Peripherals { | |||||||
|         providers.add(provider); |         providers.add(provider); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     public static void registerGenericLookup(ComponentLookup<InvalidateCallback> lookup) { | ||||||
|  |         genericProvider.registerLookup(lookup); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * A {@link ComponentLookup} for {@linkplain Capability capabilities}. | ||||||
|  |      * <p> | ||||||
|  |      * 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 <T>        The type of the capability we look up. | ||||||
|  |      */ | ||||||
|  |     private record CapabilityLookup<T>(Capability<T> capability) implements ComponentLookup<InvalidateCallback> { | ||||||
|  |         @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 |     @Nullable | ||||||
|     public static IPeripheral getPeripheral(Level world, BlockPos pos, Direction side, NonNullConsumer<Object> 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; |         return world.isInWorldBounds(pos) && !world.isClientSide ? getPeripheralAt(world, pos, side, invalidate) : null; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Nullable |     @Nullable | ||||||
|     private static IPeripheral getPeripheralAt(Level world, BlockPos pos, Direction side, NonNullConsumer<? super Object> invalidate) { |     private static IPeripheral getPeripheralAt(Level world, BlockPos pos, Direction side, InvalidateCallback invalidate) { | ||||||
|         var block = world.getBlockEntity(pos); |         var block = world.getBlockEntity(pos); | ||||||
|         if (block != null) { |         if (block != null) { | ||||||
|             var peripheral = block.getCapability(CAPABILITY_PERIPHERAL, side); |             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); | ||||||
|     } |     } | ||||||
| 
 |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -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<Capability<?>> 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<Object> 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); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -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. | ||||||
|  |  * <p> | ||||||
|  |  * 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<Object> { | ||||||
|  |     @Override | ||||||
|  |     default void accept(Object o) { | ||||||
|  |         run(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Cast this callback to a {@link NonNullConsumer} of an arbitrary type. | ||||||
|  |      * | ||||||
|  |      * @param <T> The type of the consumer, normally a {@link LazyOptional}. | ||||||
|  |      * @return {@code this}, but with a compatible type. | ||||||
|  |      */ | ||||||
|  |     @SuppressWarnings("unchecked") | ||||||
|  |     default <T> NonNullConsumer<T> castConsumer() { | ||||||
|  |         return (NonNullConsumer<T>) this; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -63,7 +63,6 @@ import net.minecraftforge.common.crafting.CraftingHelper; | |||||||
| import net.minecraftforge.common.crafting.conditions.ICondition; | import net.minecraftforge.common.crafting.conditions.ICondition; | ||||||
| import net.minecraftforge.common.crafting.conditions.ModLoadedCondition; | import net.minecraftforge.common.crafting.conditions.ModLoadedCondition; | ||||||
| import net.minecraftforge.common.extensions.IForgeMenuType; | import net.minecraftforge.common.extensions.IForgeMenuType; | ||||||
| import net.minecraftforge.common.util.NonNullConsumer; |  | ||||||
| import net.minecraftforge.event.ForgeEventFactory; | import net.minecraftforge.event.ForgeEventFactory; | ||||||
| import net.minecraftforge.eventbus.api.Event; | import net.minecraftforge.eventbus.api.Event; | ||||||
| import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; | import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; | ||||||
| @@ -429,20 +428,19 @@ public class PlatformHelperImpl implements PlatformHelper { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private abstract static class ComponentAccessImpl<T> implements ComponentAccess<T> { |     private abstract static class ComponentAccessImpl<T> implements ComponentAccess<T> { | ||||||
|         private final NonNullConsumer<Object>[] invalidators; |         private final InvalidateCallback[] invalidators; | ||||||
|         private @Nullable Level level; |         private @Nullable Level level; | ||||||
|         private @Nullable BlockPos pos; |         private @Nullable BlockPos pos; | ||||||
| 
 | 
 | ||||||
|         ComponentAccessImpl(Consumer<Direction> invalidate) { |         ComponentAccessImpl(Consumer<Direction> invalidate) { | ||||||
|             // Generate a cache of invalidation functions so we can guarantee we only ever have one registered per |             // 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! |             // capability - there's no way to remove these callbacks! | ||||||
|             @SuppressWarnings({ "unchecked", "rawtypes" }) |             var invalidators = this.invalidators = new InvalidateCallback[6]; | ||||||
|             var invalidators = this.invalidators = new NonNullConsumer[6]; |             for (var dir : Direction.values()) invalidators[dir.ordinal()] = () -> invalidate.accept(dir); | ||||||
|             for (var dir : Direction.values()) invalidators[dir.ordinal()] = x -> invalidate.accept(dir); |  | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         @Nullable |         @Nullable | ||||||
|         protected abstract T get(ServerLevel world, BlockPos pos, Direction side, NonNullConsumer<Object> invalidate); |         protected abstract T get(ServerLevel world, BlockPos pos, Direction side, InvalidateCallback invalidate); | ||||||
| 
 | 
 | ||||||
|         @Nullable |         @Nullable | ||||||
|         @Override |         @Override | ||||||
| @@ -463,7 +461,7 @@ public class PlatformHelperImpl implements PlatformHelper { | |||||||
| 
 | 
 | ||||||
|         @Nullable |         @Nullable | ||||||
|         @Override |         @Override | ||||||
|         protected IPeripheral get(ServerLevel world, BlockPos pos, Direction side, NonNullConsumer<Object> invalidate) { |         protected IPeripheral get(ServerLevel world, BlockPos pos, Direction side, InvalidateCallback invalidate) { | ||||||
|             return Peripherals.getPeripheral(world, pos, side, invalidate); |             return Peripherals.getPeripheral(world, pos, side, invalidate); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| @@ -478,7 +476,7 @@ public class PlatformHelperImpl implements PlatformHelper { | |||||||
| 
 | 
 | ||||||
|         @Nullable |         @Nullable | ||||||
|         @Override |         @Override | ||||||
|         protected T get(ServerLevel world, BlockPos pos, Direction side, NonNullConsumer<Object> invalidate) { |         protected T get(ServerLevel world, BlockPos pos, Direction side, InvalidateCallback invalidate) { | ||||||
|             if (!world.isLoaded(pos)) return null; |             if (!world.isLoaded(pos)) return null; | ||||||
| 
 | 
 | ||||||
|             var blockEntity = world.getBlockEntity(pos); |             var blockEntity = world.getBlockEntity(pos); | ||||||
|   | |||||||
| @@ -4,11 +4,11 @@ | |||||||
| 
 | 
 | ||||||
| package dan200.computercraft.shared.util; | package dan200.computercraft.shared.util; | ||||||
| 
 | 
 | ||||||
|  | import dan200.computercraft.shared.platform.InvalidateCallback; | ||||||
| import net.minecraft.core.Direction; | import net.minecraft.core.Direction; | ||||||
| import net.minecraftforge.common.capabilities.Capability; | import net.minecraftforge.common.capabilities.Capability; | ||||||
| import net.minecraftforge.common.capabilities.ICapabilityProvider; | import net.minecraftforge.common.capabilities.ICapabilityProvider; | ||||||
| import net.minecraftforge.common.util.LazyOptional; | import net.minecraftforge.common.util.LazyOptional; | ||||||
| import net.minecraftforge.common.util.NonNullConsumer; |  | ||||||
| 
 | 
 | ||||||
| import javax.annotation.Nullable; | import javax.annotation.Nullable; | ||||||
| 
 | 
 | ||||||
| @@ -32,18 +32,11 @@ public final class CapabilityUtil { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public static <T> void addListener(LazyOptional<T> p, NonNullConsumer<? super LazyOptional<T>> 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<LazyOptional<T>>) invalidate; |  | ||||||
|         p.addListener(safeInvalidate); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Nullable |     @Nullable | ||||||
|     public static <T> T unwrap(LazyOptional<T> p, NonNullConsumer<? super LazyOptional<T>> invalidate) { |     public static <T> T unwrap(LazyOptional<T> p, InvalidateCallback invalidate) { | ||||||
|         if (!p.isPresent()) return null; |         if (!p.isPresent()) return null; | ||||||
| 
 | 
 | ||||||
|         addListener(p, invalidate); |         p.addListener(invalidate.castConsumer()); | ||||||
|         return p.orElseThrow(NullPointerException::new); |         return p.orElseThrow(NullPointerException::new); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Jonathan Coates
					Jonathan Coates