1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2024-06-25 22:53:22 +00:00

Access capabilities using PlatformHelper

We provide a new abstraction: ComponentAccess, which is a view of
capabilities (or block lookups for Fabric) for adjacent blocks.
This commit is contained in:
Jonathan Coates 2022-11-08 17:44:06 +00:00
parent 320007dbc6
commit 7d47b219c5
No known key found for this signature in database
GPG Key ID: B9E431FF07C98D06
10 changed files with 190 additions and 43 deletions

View File

@ -111,7 +111,8 @@ minecraft {
mods.register("cctest") {
source(sourceSets["testMod"])
source(sourceSets["testFixtures"])
// FIXME: We need this for running in-dev but not from Gradle: source(project(":core").sourceSets.testFixtures.get())
// FIXME: We need this for running in-dev but not from Gradle:
// source(project(":core").sourceSets.testFixtures.get())
}
}

View File

@ -11,11 +11,11 @@
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraftforge.common.util.NonNullSupplier;
import javax.annotation.Nonnull;
import java.util.List;
import java.util.function.IntSupplier;
import java.util.function.Supplier;
/**
* Version of {@link net.minecraft.client.gui.components.ImageButton} which allows changing some properties
@ -29,7 +29,7 @@ public class DynamicImageButton extends Button {
private final int yDiffTex;
private final int textureWidth;
private final int textureHeight;
private final NonNullSupplier<List<Component>> tooltip;
private final Supplier<List<Component>> tooltip;
public DynamicImageButton(
Screen screen, int x, int y, int width, int height, int xTexStart, int yTexStart, int yDiffTex,
@ -47,7 +47,7 @@ public DynamicImageButton(
public DynamicImageButton(
Screen screen, int x, int y, int width, int height, IntSupplier xTexStart, int yTexStart, int yDiffTex,
ResourceLocation texture, int textureWidth, int textureHeight,
OnPress onPress, NonNullSupplier<List<Component>> tooltip
OnPress onPress, Supplier<List<Component>> tooltip
) {
super(x, y, width, height, Component.empty(), onPress);
this.screen = screen;

View File

@ -5,25 +5,28 @@
*/
package dan200.computercraft.shared.computer.blocks;
import com.google.common.base.Strings;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.core.computer.ComputerSide;
import dan200.computercraft.shared.BundledRedstone;
import dan200.computercraft.shared.Peripherals;
import dan200.computercraft.shared.common.TileGeneric;
import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.computer.core.ComputerState;
import dan200.computercraft.shared.computer.core.ServerComputer;
import dan200.computercraft.shared.computer.core.ServerContext;
import dan200.computercraft.shared.network.container.ComputerContainerData;
import dan200.computercraft.shared.platform.ComponentAccess;
import dan200.computercraft.shared.platform.PlatformHelper;
import dan200.computercraft.shared.util.DirectionUtil;
import dan200.computercraft.shared.util.IDAssigner;
import dan200.computercraft.shared.util.RedstoneUtil;
import joptsimple.internal.Strings;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.*;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
@ -32,7 +35,6 @@
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraftforge.common.util.NonNullConsumer;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
@ -51,7 +53,7 @@ public abstract class TileComputerBase extends TileGeneric implements IComputerT
private boolean fresh = false;
private int invalidSides = 0;
private final NonNullConsumer<Object>[] invalidate;
private final ComponentAccess<IPeripheral> peripherals = PlatformHelper.get().createPeripheralAccess(d -> invalidSides |= 1 << d.ordinal());
private LockCode lockCode = LockCode.NO_LOCK;
@ -60,14 +62,6 @@ public abstract class TileComputerBase extends TileGeneric implements IComputerT
public TileComputerBase(BlockEntityType<? extends TileGeneric> type, BlockPos pos, BlockState state, ComputerFamily family) {
super(type, pos, state);
this.family = family;
// We cache these so we can guarantee we only ever register one listener for adjacent capabilities.
@SuppressWarnings({ "unchecked", "rawtypes" })
var invalidate = this.invalidate = new NonNullConsumer[6];
for (var direction : Direction.values()) {
var mask = 1 << direction.ordinal();
invalidate[direction.ordinal()] = o -> invalidSides |= mask;
}
}
protected void unload() {
@ -231,8 +225,7 @@ private void refreshPeripheral(@Nonnull ServerComputer computer, Direction dir)
var localDir = remapToLocalSide(dir);
if (isPeripheralBlockedOnSide(localDir)) return;
var offsetSide = dir.getOpposite();
var peripheral = Peripherals.getPeripheral(getLevel(), getBlockPos().relative(dir), offsetSide, invalidate[dir.ordinal()]);
var peripheral = peripherals.get((ServerLevel) getLevel(), getBlockPos(), dir);
computer.setPeripheral(localDir, peripheral);
}

View File

@ -6,9 +6,9 @@
package dan200.computercraft.shared.peripheral.modem.wired;
import com.google.common.collect.ImmutableMap;
import dan200.computercraft.api.ForgeComputerCraftAPI;
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.common.BlockGeneric;
import dan200.computercraft.shared.platform.PlatformHelper;
import dan200.computercraft.shared.util.WaterloggableHelpers;
import dan200.computercraft.shared.util.WorldUtil;
import net.minecraft.core.BlockPos;
@ -76,10 +76,10 @@ public static boolean canConnectIn(BlockState state, Direction direction) {
return state.getValue(BlockCable.CABLE) && state.getValue(BlockCable.MODEM).getFacing() != direction;
}
public static boolean doesConnectVisually(BlockState state, BlockGetter world, BlockPos pos, Direction direction) {
public static boolean doesConnectVisually(BlockState state, Level level, BlockPos pos, Direction direction) {
if (!state.getValue(CABLE)) return false;
if (state.getValue(MODEM).getFacing() == direction) return true;
return ForgeComputerCraftAPI.getWiredElementAt(world, pos.relative(direction), direction.getOpposite()).isPresent();
return PlatformHelper.get().hasWiredElementIn(level, pos, direction);
}
@Nonnull
@ -171,7 +171,7 @@ public BlockState updateShape(@Nonnull BlockState state, @Nonnull Direction side
return getFluidState(state).createLegacyBlock();
}
return state.setValue(CONNECTIONS.get(side), doesConnectVisually(state, world, pos, side));
return world instanceof Level level ? state.setValue(CONNECTIONS.get(side), doesConnectVisually(state, level, pos, side)) : state;
}
@Override

View File

@ -6,7 +6,6 @@
package dan200.computercraft.shared.peripheral.modem.wired;
import com.google.common.base.Objects;
import dan200.computercraft.api.ForgeComputerCraftAPI;
import dan200.computercraft.api.network.wired.IWiredElement;
import dan200.computercraft.api.network.wired.IWiredNode;
import dan200.computercraft.api.peripheral.IPeripheral;
@ -14,12 +13,15 @@
import dan200.computercraft.shared.command.text.ChatHelpers;
import dan200.computercraft.shared.common.TileGeneric;
import dan200.computercraft.shared.peripheral.modem.ModemState;
import dan200.computercraft.shared.platform.ComponentAccess;
import dan200.computercraft.shared.platform.PlatformHelper;
import dan200.computercraft.shared.util.DirectionUtil;
import dan200.computercraft.shared.util.TickScheduler;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.player.Player;
@ -30,8 +32,6 @@
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.Vec3;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.common.util.NonNullConsumer;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
@ -99,7 +99,7 @@ public Object getTarget() {
}
};
private final NonNullConsumer<LazyOptional<IWiredElement>> connectedNodeChanged = x -> connectionsChanged();
private final ComponentAccess<IWiredElement> connectedElements = PlatformHelper.get().createWiredElementAccess(x -> connectionsChanged());
public TileCable(BlockEntityType<? extends TileCable> type, BlockPos pos, BlockState state) {
super(type, pos, state);
@ -284,11 +284,10 @@ void connectionsChanged() {
var offset = current.relative(facing);
if (!world.isLoaded(offset)) continue;
var element = ForgeComputerCraftAPI.getWiredElementAt(world, offset, facing.getOpposite());
if (!element.isPresent()) continue;
var element = connectedElements.get((ServerLevel) world, current, facing);
if (element == null) continue;
element.addListener(connectedNodeChanged);
var node = element.orElseThrow(NullPointerException::new).getNode();
var node = element.getNode();
if (BlockCable.canConnectIn(state, facing)) {
// If we can connect to it then do so
this.node.connectTo(node);

View File

@ -6,19 +6,21 @@
package dan200.computercraft.shared.peripheral.modem.wired;
import com.google.common.base.Objects;
import dan200.computercraft.api.ForgeComputerCraftAPI;
import dan200.computercraft.api.network.wired.IWiredElement;
import dan200.computercraft.api.network.wired.IWiredNode;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.shared.command.text.ChatHelpers;
import dan200.computercraft.shared.common.TileGeneric;
import dan200.computercraft.shared.peripheral.modem.ModemState;
import dan200.computercraft.shared.platform.ComponentAccess;
import dan200.computercraft.shared.platform.PlatformHelper;
import dan200.computercraft.shared.util.DirectionUtil;
import dan200.computercraft.shared.util.TickScheduler;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.player.Player;
@ -27,8 +29,6 @@
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.Vec3;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.common.util.NonNullConsumer;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
@ -89,7 +89,7 @@ public Vec3 getPosition() {
private final WiredModemElement element = new FullElement(this);
private final IWiredNode node = element.getNode();
private final NonNullConsumer<LazyOptional<IWiredElement>> connectedNodeChanged = x -> connectionsChanged();
private final ComponentAccess<IWiredElement> connectedElements = PlatformHelper.get().createWiredElementAccess(x -> connectionsChanged());
private int invalidSides = 0;
@ -252,11 +252,10 @@ private void connectionsChanged() {
var offset = current.relative(facing);
if (!world.isLoaded(offset)) continue;
var element = ForgeComputerCraftAPI.getWiredElementAt(world, offset, facing.getOpposite());
if (!element.isPresent()) continue;
var element = connectedElements.get((ServerLevel) getLevel(), getBlockPos(), facing);
if (element == null) return;
element.addListener(connectedNodeChanged);
node.connectTo(element.orElseThrow(NullPointerException::new).getNode());
node.connectTo(element.getNode());
}
}

View File

@ -7,14 +7,15 @@
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.Peripherals;
import dan200.computercraft.shared.computer.core.ServerContext;
import dan200.computercraft.shared.platform.ComponentAccess;
import dan200.computercraft.shared.platform.PlatformHelper;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.Tag;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.Level;
import net.minecraftforge.common.util.NonNullConsumer;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
@ -35,10 +36,10 @@ public final class WiredModemLocalPeripheral {
private String type;
private IPeripheral peripheral;
private final NonNullConsumer<Object> invalidate;
private final ComponentAccess<IPeripheral> peripherals;
public WiredModemLocalPeripheral(@Nonnull Runnable invalidate) {
this.invalidate = x -> invalidate.run();
peripherals = PlatformHelper.get().createPeripheralAccess(x -> invalidate.run());
}
/**
@ -126,7 +127,7 @@ private IPeripheral getPeripheralFrom(Level world, BlockPos pos, Direction direc
var block = world.getBlockState(offset).getBlock();
if (block == ModRegistry.Blocks.WIRED_MODEM_FULL.get() || block == ModRegistry.Blocks.CABLE.get()) return null;
var peripheral = Peripherals.getPeripheral(world, offset, direction.getOpposite(), invalidate);
var peripheral = peripherals.get((ServerLevel) world, pos, direction);
return peripheral instanceof WiredModemPeripheral ? null : peripheral;
}
}

View File

@ -0,0 +1,33 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.shared.platform;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.server.level.ServerLevel;
import javax.annotation.Nullable;
/**
* A (possibly cached) provider of a component at a specific location.
*
* @param <T> The type of the component.
*/
public interface ComponentAccess<T> {
/**
* Get a peripheral for the current block.
* <p>
* Both {@code level} and {@code pos} must be constant for the lifetime of the store.
*
* @param level The current level.
* @param pos The position of the block fetching the peripheral, for instance the computer or modem.
* @param direction The direction the peripheral is in.
* @return The peripheral, or {@literal null} if not found.
* @throws IllegalStateException If the level or position have changed.
*/
@Nullable
T get(ServerLevel level, BlockPos pos, Direction direction);
}

View File

@ -5,10 +5,13 @@
*/
package dan200.computercraft.shared.platform;
import dan200.computercraft.api.network.wired.IWiredElement;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.shared.network.NetworkMessage;
import dan200.computercraft.shared.network.client.ClientNetworkContext;
import dan200.computercraft.shared.network.container.ContainerData;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.Registry;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceKey;
@ -20,6 +23,7 @@
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.inventory.MenuType;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
@ -30,6 +34,7 @@
import javax.annotation.Nullable;
import java.util.Collection;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
/**
@ -146,4 +151,34 @@ static PlatformHelper get() {
* @param chunk The chunk players must be tracking.
*/
void sendToAllTracking(NetworkMessage<ClientNetworkContext> message, LevelChunk chunk);
/**
* Create a {@link ComponentAccess} for surrounding peripherals.
*
* @param invalidate The function to call when a neighbouring peripheral potentially changes. This <em>MAY NOT</em>
* include all changes, and so block updates should still be listened to.
* @return The peripheral component access.
*/
ComponentAccess<IPeripheral> createPeripheralAccess(Consumer<Direction> invalidate);
/**
* Create a {@link ComponentAccess} for surrounding wired nodes.
*
* @param invalidate The function to call when a neighbouring wired node potentially changes. This <em>MAY NOT</em>
* include all changes, and so block updates should still be listened to.
* @return The peripheral component access.
*/
ComponentAccess<IWiredElement> createWiredElementAccess(Consumer<Direction> invalidate);
/**
* Determine if there is a wired element in the given direction. This is equivalent to
* {@code createWiredElementAt(x -> {}).get(level, pos, dir) != null}, but is intended for when we don't need the
* cache.
*
* @param level The current level.
* @param pos The <em>current</em> block's position.
* @param direction The direction to check in.
* @return Whether there is a wired element in the given direction.
*/
boolean hasWiredElementIn(Level level, BlockPos pos, Direction direction);
}

View File

@ -7,10 +7,16 @@
import com.google.auto.service.AutoService;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.network.wired.IWiredElement;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.shared.Capabilities;
import dan200.computercraft.shared.Peripherals;
import dan200.computercraft.shared.network.NetworkMessage;
import dan200.computercraft.shared.network.client.ClientNetworkContext;
import dan200.computercraft.shared.network.container.ContainerData;
import dan200.computercraft.shared.util.CapabilityUtil;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.Registry;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.FriendlyByteBuf;
@ -24,13 +30,16 @@
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.inventory.MenuType;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.LevelChunk;
import net.minecraft.world.phys.Vec3;
import net.minecraftforge.common.capabilities.Capability;
import net.minecraftforge.common.extensions.IForgeMenuType;
import net.minecraftforge.common.util.NonNullConsumer;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
import net.minecraftforge.network.NetworkHooks;
import net.minecraftforge.registries.DeferredRegister;
@ -43,6 +52,7 @@
import java.util.Iterator;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
@ -118,6 +128,24 @@ public void sendToAllTracking(NetworkMessage<ClientNetworkContext> message, Leve
NetworkHandler.sendToAllTracking(message, chunk);
}
@Override
public ComponentAccess<IPeripheral> createPeripheralAccess(Consumer<Direction> invalidate) {
return new PeripheralAccess(invalidate);
}
@Override
public ComponentAccess<IWiredElement> createWiredElementAccess(Consumer<Direction> invalidate) {
return new CapabilityAccess<>(Capabilities.CAPABILITY_WIRED_ELEMENT, invalidate);
}
@Override
public boolean hasWiredElementIn(Level level, BlockPos pos, Direction direction) {
if (!level.isLoaded(pos)) return false;
var blockEntity = level.getBlockEntity(pos.relative(direction));
return blockEntity != null && blockEntity.getCapability(Capabilities.CAPABILITY_WIRED_ELEMENT, direction.getOpposite()).isPresent();
}
@Nullable
@Override
public CompoundTag getShareTag(ItemStack item) {
@ -190,4 +218,62 @@ public T get() {
return object.get();
}
}
private abstract static class ComponentAccessImpl<T> implements ComponentAccess<T> {
private final NonNullConsumer<Object>[] 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);
}
@Nullable
protected abstract T get(ServerLevel world, BlockPos pos, Direction side, NonNullConsumer<Object> invalidate);
@Nullable
@Override
public T get(ServerLevel level, BlockPos pos, Direction direction) {
if (this.level != null && this.level != level) throw new IllegalStateException("Level has changed");
if (this.pos != null && this.pos != pos) throw new IllegalStateException("Position has changed");
this.level = level;
this.pos = pos;
return get(level, pos.relative(direction), direction.getOpposite(), invalidators[direction.ordinal()]);
}
}
private static class PeripheralAccess extends ComponentAccessImpl<IPeripheral> {
PeripheralAccess(Consumer<Direction> invalidate) {
super(invalidate);
}
@Nullable
@Override
protected IPeripheral get(ServerLevel world, BlockPos pos, Direction side, NonNullConsumer<Object> invalidate) {
return Peripherals.getPeripheral(world, pos, side, invalidate);
}
}
private static class CapabilityAccess<T> extends ComponentAccessImpl<T> {
private final Capability<T> capability;
CapabilityAccess(Capability<T> capability, Consumer<Direction> invalidate) {
super(invalidate);
this.capability = capability;
}
@Nullable
@Override
protected T get(ServerLevel world, BlockPos pos, Direction side, NonNullConsumer<Object> invalidate) {
if (!world.isLoaded(pos)) return null;
var blockEntity = world.getBlockEntity(pos);
return blockEntity != null ? CapabilityUtil.unwrap(blockEntity.getCapability(capability, side), invalidate) : null;
}
}
}