Skip to content

Commit

Permalink
Unify the generic peirpheral system a litte
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
SquidDev committed Oct 17, 2023
1 parent 1747c74 commit 0ff58cd
Show file tree
Hide file tree
Showing 13 changed files with 245 additions and 157 deletions.
Original file line number Diff line number Diff line change
@@ -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);
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,9 @@

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;
Expand All @@ -28,16 +25,10 @@
* See the platform-specific peripheral providers for the usage of this.
*/
final class GenericPeripheralBuilder {
private final MethodSupplier<PeripheralMethod> peripheralMethods;

private @Nullable String name;
private final Set<String> additionalTypes = new HashSet<>(0);
private final ArrayList<SaturatedMethod> methods = new ArrayList<>();

GenericPeripheralBuilder(MinecraftServer server) {
peripheralMethods = ServerContext.get(server).peripheralMethods();
}

@Nullable
IPeripheral toPeripheral(BlockEntity blockEntity, Direction side) {
if (methods.isEmpty()) return null;
Expand All @@ -46,18 +37,16 @@ IPeripheral toPeripheral(BlockEntity blockEntity, Direction side) {
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));

// 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());
});
void addMethod(Object target, String name, PeripheralMethod method, @Nullable NamedMethod<PeripheralMethod> 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());
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
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;
Expand Down Expand Up @@ -100,6 +101,8 @@ public static void init() {
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)
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@
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;
Expand Down Expand Up @@ -202,7 +202,7 @@ public void sendToAllTracking(NetworkMessage<ClientNetworkContext> message, Leve

@Override
public ComponentAccess<IPeripheral> createPeripheralAccess(Consumer<Direction> invalidate) {
return new PeripheralAccessImpl();
return new PeripheralAccessImpl(invalidate);
}

@Override
Expand Down Expand Up @@ -476,8 +476,11 @@ public T get(ServerLevel level, BlockPos pos, Direction direction) {
}

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());
for (var dir : Direction.values()) invalidators[dir.ordinal()] = () -> invalidate.accept(dir);
}

@Nullable
Expand All @@ -487,7 +490,8 @@ public IPeripheral get(ServerLevel level, BlockPos pos, Direction direction) {
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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
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;
Expand Down Expand Up @@ -45,7 +44,7 @@ public void registerPeripheralProvider(IPeripheralProvider provider) {

@Override
public void registerGenericCapability(Capability<?> capability) {
GenericPeripheralProvider.addCapability(capability);
Peripherals.registerGenericCapability(capability);
}

@Override
Expand Down
Loading

0 comments on commit 0ff58cd

Please sign in to comment.