1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2025-11-15 20:47:11 +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:
Jonathan Coates
2023-10-17 21:59:16 +01:00
parent 1747c74770
commit 0ff58cdc3e
13 changed files with 244 additions and 156 deletions

View File

@@ -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

View File

@@ -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.
* <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 Logger LOG = LoggerFactory.getLogger(Peripherals.class);
private static final Collection<IPeripheralProvider> providers = new LinkedHashSet<>();
private static final GenericPeripheralProvider<InvalidateCallback> genericProvider = new GenericPeripheralProvider<>();
private Peripherals() {
}
@@ -35,13 +45,39 @@ public final class Peripherals {
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
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;
}
@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);
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);
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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<T> implements ComponentAccess<T> {
private final NonNullConsumer<Object>[] invalidators;
private final InvalidateCallback[] invalidators;
private @Nullable Level level;
private @Nullable BlockPos pos;
ComponentAccessImpl(Consumer<Direction> 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<Object> 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<Object> 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<Object> invalidate) {
protected T get(ServerLevel world, BlockPos pos, Direction side, InvalidateCallback invalidate) {
if (!world.isLoaded(pos)) return null;
var blockEntity = world.getBlockEntity(pos);

View File

@@ -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 <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
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;
addListener(p, invalidate);
p.addListener(invalidate.castConsumer());
return p.orElseThrow(NullPointerException::new);
}