Put some more effort into avoiding listeners keeping references to things that may otherwise be garbage collected.

This commit is contained in:
Florian Nücke
2022-01-02 15:11:53 +01:00
parent 143be7cea1
commit b656e27f43
28 changed files with 428 additions and 192 deletions

View File

@@ -1,7 +1,7 @@
package li.cil.oc2.api.bus.device.provider;
import li.cil.oc2.api.bus.device.Device;
import net.minecraftforge.common.util.LazyOptional;
import li.cil.oc2.api.util.Invalidatable;
import net.minecraftforge.registries.IForgeRegistryEntry;
/**
@@ -47,5 +47,5 @@ public interface BlockDeviceProvider extends IForgeRegistryEntry<BlockDeviceProv
* @param query the query describing the object to get a {@link Device} for.
* @return a device for the specified query, if available.
*/
LazyOptional<Device> getDevice(BlockDeviceQuery query);
Invalidatable<Device> getDevice(BlockDeviceQuery query);
}

View File

@@ -0,0 +1,107 @@
package li.cil.oc2.api.util;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;
/**
* Wrapper for objects which may become invalid, such as {@link li.cil.oc2.api.bus.device.Device}s.
* <p>
* This implementation allows listeners added via {@link #addListener(Consumer)} to be removed again
* using the returned token. This allows avoiding memory leaks due to inversion of reference ownership,
* where an observable keeps observers alive due to its listener list.
*
* @param <T> The type of the underlying value.
*/
public final class Invalidatable<T> {
@FunctionalInterface
public interface ListenerToken {
void removeListener();
}
@SuppressWarnings("rawtypes")
private static final Invalidatable EMPTY = new Invalidatable();
@SuppressWarnings("unchecked")
public static <T> Invalidatable<T> empty() {
return EMPTY;
}
public static <T> Invalidatable<T> of(final T value) {
return new Invalidatable<>(value);
}
private final List<Consumer<Invalidatable<T>>> listeners = new ArrayList<>();
private T value;
private boolean isValid = true;
public Invalidatable(final T value) {
this.value = value;
}
private Invalidatable() {
this.value = null;
this.isValid = false;
}
public T get() {
if (isValid) {
assert value != null;
return value;
} else {
throw new IllegalStateException();
}
}
public boolean isPresent() {
return isValid;
}
public void ifPresent(final Consumer<T> consumer) {
if (isValid) {
consumer.accept(value);
}
}
public <U> Invalidatable<U> mapWithDependency(final Function<T, U> mapper) {
if (!isValid) {
return empty();
}
// Map to new type.
final Invalidatable<U> mapped = new Invalidatable<>(mapper.apply(value));
// When this instance gets invalidated, invalidate mapped value.
final ListenerToken token = this.addListener(unused -> mapped.invalidate());
// When mapped value gets invalidated, remove listener.
mapped.addListener(unused -> token.removeListener());
return mapped;
}
public void invalidate() {
if (isValid) {
isValid = false;
value = null;
listeners.forEach(listener -> listener.accept(this));
listeners.clear();
}
}
public ListenerToken addListener(final Consumer<Invalidatable<T>> listener) {
if (isValid) {
listeners.add(listener);
return () -> {
if (isValid) {
listeners.remove(listener);
}
};
} else {
listener.accept(this);
return () -> {
};
}
}
}

View File

@@ -56,7 +56,7 @@ public final class NetworkConnectorBlock extends FaceAttachedHorizontalDirection
if (Objects.equals(changedBlockPos, pos.relative(getFacing(state).getOpposite()))) {
final BlockEntity blockEntity = level.getBlockEntity(pos);
if (blockEntity instanceof final NetworkConnectorBlockEntity networkConnector) {
networkConnector.setLocalInterfaceChanged();
networkConnector.setNeighborChanged();
}
}
}

View File

@@ -265,12 +265,12 @@ public final class BusCableBlockEntity extends ModBlockEntity {
}
@Override
protected void collectSyntheticDevices(final Level level, final BlockPos pos, @Nullable final Direction direction, final HashSet<BlockDeviceInfo> devices) {
super.collectSyntheticDevices(level, pos, direction, devices);
protected void collectSyntheticDevices(final Level level, final BlockPos pos, @Nullable final Direction direction, final HashSet<BlockEntry> entries) {
super.collectSyntheticDevices(level, pos, direction, entries);
if (direction != null) {
final String interfaceName = interfaceNames[direction.get3DDataValue()];
if (!StringUtil.isNullOrEmpty(interfaceName)) {
devices.add(new BlockDeviceInfo(null, new TypeNameRPCDevice(interfaceName)));
entries.add(new BlockEntry(new BlockDeviceInfo(null, new TypeNameRPCDevice(interfaceName)), pos));
}
}
}

View File

@@ -11,7 +11,6 @@ import li.cil.oc2.common.block.ComputerBlock;
import li.cil.oc2.common.bus.BlockEntityDeviceBusController;
import li.cil.oc2.common.bus.BlockEntityDeviceBusElement;
import li.cil.oc2.common.bus.CommonDeviceBusController;
import li.cil.oc2.common.bus.device.util.BlockDeviceInfo;
import li.cil.oc2.common.bus.device.util.Devices;
import li.cil.oc2.common.capabilities.Capabilities;
import li.cil.oc2.common.container.ComputerInventoryContainer;
@@ -338,9 +337,9 @@ public final class ComputerBlockEntity extends ModBlockEntity implements Termina
public void addOwnDevices() {
assert level != null;
for (final BlockDeviceInfo info : collectDevices(level, getPosition(), null)) {
devices.add(info.device);
super.addDevice(info.device);
for (final BlockEntry info : collectDevices(level, getPosition(), null)) {
devices.add(info.getDevice());
super.addDevice(info.getDevice());
}
}

View File

@@ -1,5 +1,6 @@
package li.cil.oc2.common.blockentity;
import li.cil.oc2.common.util.LazyOptionalUtils;
import li.cil.oc2.common.util.ServerScheduler;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
@@ -36,10 +37,14 @@ public abstract class ModBlockEntity extends BlockEntity {
}
final CapabilityCacheKey key = new CapabilityCacheKey(capability, side);
final LazyOptional<?> value;
LazyOptional<?> value;
if (capabilityCache.containsKey(key)) {
value = capabilityCache.get(key);
} else {
value = LazyOptional.empty();
}
if (!value.isPresent()) {
final ArrayList<T> list = new ArrayList<>();
collectCapabilities(new CapabilityCollector() {
@SuppressWarnings("unchecked")
@@ -60,7 +65,7 @@ public abstract class ModBlockEntity extends BlockEntity {
if (value.isPresent()) {
capabilityCache.put(key, value);
value.addListener(optional -> capabilityCache.remove(key, optional));
LazyOptionalUtils.addWeakListener(value, capabilityCache, (map, optional) -> map.remove(key, optional));
}
}

View File

@@ -9,6 +9,7 @@ import li.cil.oc2.common.item.Items;
import li.cil.oc2.common.network.Network;
import li.cil.oc2.common.network.message.NetworkConnectorConnectionsMessage;
import li.cil.oc2.common.util.ItemStackUtils;
import li.cil.oc2.common.util.LazyOptionalUtils;
import li.cil.oc2.common.util.NBTTagIds;
import li.cil.oc2.common.util.ServerScheduler;
import net.minecraft.client.Minecraft;
@@ -62,8 +63,8 @@ public final class NetworkConnectorBlockEntity extends ModBlockEntity {
private final NetworkConnectorNetworkInterface networkInterface = new NetworkConnectorNetworkInterface();
private LazyOptional<NetworkInterface> localInterface = LazyOptional.empty();
private boolean isLocalConnectionDirty = true;
private LazyOptional<NetworkInterface> adjacentInterface = LazyOptional.empty();
private boolean isAdjacentInterfaceDirty = true;
private final HashSet<BlockPos> connectorPositions = new HashSet<>();
private final HashSet<BlockPos> ownedCables = new HashSet<>();
@@ -161,8 +162,8 @@ public final class NetworkConnectorBlockEntity extends ModBlockEntity {
return connectorPositions;
}
public void setLocalInterfaceChanged() {
isLocalConnectionDirty = true;
public void setNeighborChanged() {
isAdjacentInterfaceDirty = true;
}
@OnlyIn(Dist.CLIENT)
@@ -181,8 +182,8 @@ public final class NetworkConnectorBlockEntity extends ModBlockEntity {
return;
}
if (isLocalConnectionDirty) {
isLocalConnectionDirty = false;
if (isAdjacentInterfaceDirty) {
isAdjacentInterfaceDirty = false;
resolveLocalInterface();
}
@@ -194,7 +195,7 @@ public final class NetworkConnectorBlockEntity extends ModBlockEntity {
}
}
final NetworkInterface src = localInterface.orElse(NullNetworkInterface.INSTANCE);
final NetworkInterface src = adjacentInterface.orElse(NullNetworkInterface.INSTANCE);
int byteBudget = BYTES_PER_TICK;
byte[] frame;
@@ -324,7 +325,7 @@ public final class NetworkConnectorBlockEntity extends ModBlockEntity {
private void resolveLocalInterface() {
assert level != null;
localInterface = LazyOptional.empty();
adjacentInterface = LazyOptional.empty();
if (isRemoved()) {
return;
@@ -333,9 +334,8 @@ public final class NetworkConnectorBlockEntity extends ModBlockEntity {
final Direction facing = NetworkConnectorBlock.getFacing(getBlockState());
final BlockPos sourcePos = getBlockPos().relative(facing.getOpposite());
final ChunkPos sourceChunk = new ChunkPos(sourcePos);
if (!level.hasChunk(sourceChunk.x, sourceChunk.z)) {
ServerScheduler.schedule(level, this::setLocalInterfaceChanged, RETRY_UNLOADED_CHUNK_INTERVAL);
if (!level.isLoaded(sourcePos)) {
ServerScheduler.schedule(level, this::setNeighborChanged, RETRY_UNLOADED_CHUNK_INTERVAL);
return;
}
@@ -344,9 +344,9 @@ public final class NetworkConnectorBlockEntity extends ModBlockEntity {
return;
}
localInterface = blockEntity.getCapability(Capabilities.NETWORK_INTERFACE, facing);
if (localInterface.isPresent()) {
localInterface.addListener(unused -> setLocalInterfaceChanged());
adjacentInterface = blockEntity.getCapability(Capabilities.NETWORK_INTERFACE, facing);
if (adjacentInterface.isPresent()) {
LazyOptionalUtils.addWeakListener(adjacentInterface, this, (connector, unused) -> connector.setNeighborChanged());
}
}
@@ -449,7 +449,7 @@ public final class NetworkConnectorBlockEntity extends ModBlockEntity {
return;
}
localInterface.ifPresent(dst -> {
adjacentInterface.ifPresent(dst -> {
if (dst == source) {
return;
}

View File

@@ -3,6 +3,8 @@ package li.cil.oc2.common.blockentity;
import li.cil.oc2.api.capabilities.NetworkInterface;
import li.cil.oc2.common.Constants;
import li.cil.oc2.common.capabilities.Capabilities;
import li.cil.oc2.common.util.LazyOptionalUtils;
import li.cil.oc2.common.util.LevelUtils;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.world.level.block.entity.BlockEntity;
@@ -61,6 +63,10 @@ public final class NetworkHubBlockEntity extends ModBlockEntity implements Netwo
return;
}
for (final Direction side : Constants.DIRECTIONS) {
adjacentInterfaces[side.get3DDataValue()] = null;
}
areAdjacentInterfacesDirty = false;
if (level == null || level.isClientSide()) {
@@ -69,14 +75,12 @@ public final class NetworkHubBlockEntity extends ModBlockEntity implements Netwo
final BlockPos pos = getBlockPos();
for (final Direction side : Constants.DIRECTIONS) {
adjacentInterfaces[side.get3DDataValue()] = null;
final BlockEntity neighborBlockEntity = level.getBlockEntity(pos.relative(side));
final BlockEntity neighborBlockEntity = LevelUtils.getBlockEntityIfChunkExists(level, pos.relative(side));
if (neighborBlockEntity != null) {
final LazyOptional<NetworkInterface> capability = neighborBlockEntity.getCapability(Capabilities.NETWORK_INTERFACE, side.getOpposite());
capability.ifPresent(adjacentInterface -> {
final LazyOptional<NetworkInterface> optional = neighborBlockEntity.getCapability(Capabilities.NETWORK_INTERFACE, side.getOpposite());
optional.ifPresent(adjacentInterface -> {
adjacentInterfaces[side.get3DDataValue()] = adjacentInterface;
capability.addListener(unused -> handleNeighborChanged());
LazyOptionalUtils.addWeakListener(optional, this, (hub, unused) -> hub.handleNeighborChanged());
});
}
}

View File

@@ -1,10 +0,0 @@
package li.cil.oc2.common.bus;
import li.cil.oc2.api.bus.device.provider.BlockDeviceProvider;
import li.cil.oc2.common.bus.device.util.BlockDeviceInfo;
public abstract class AbstractGroupingBlockDeviceBusElement extends AbstractGroupingDeviceBusElement<BlockDeviceProvider, BlockDeviceInfo> {
public AbstractGroupingBlockDeviceBusElement(final int groupCount) {
super(groupCount);
}
}

View File

@@ -1,24 +1,29 @@
package li.cil.oc2.common.bus;
import li.cil.oc2.api.bus.device.Device;
import li.cil.oc2.common.bus.device.util.AbstractDeviceInfo;
import li.cil.oc2.common.util.ItemDeviceUtils;
import li.cil.oc2.common.util.NBTTagIds;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import net.minecraftforge.registries.IForgeRegistryEntry;
import java.util.*;
public abstract class AbstractGroupingDeviceBusElement<TProvider extends IForgeRegistryEntry<TProvider>, TDeviceInfo extends AbstractDeviceInfo<TProvider, ?>> extends AbstractDeviceBusElement {
public abstract class AbstractGroupingDeviceBusElement<T extends AbstractGroupingDeviceBusElement.Entry> extends AbstractDeviceBusElement {
private static final String GROUPS_TAG_NAME = "groups";
private static final String GROUP_ID_TAG_NAME = "groupId";
private static final String GROUP_DATA_TAG_NAME = "groupData";
protected interface Entry {
Optional<String> getDeviceDataKey();
OptionalInt getDeviceEnergyConsumption();
Device getDevice();
}
///////////////////////////////////////////////////////////////////
protected final int groupCount;
protected final ArrayList<HashSet<TDeviceInfo>> groups;
protected final ArrayList<HashSet<T>> groups;
///////////////////////////////////////////////////////////////////
@@ -42,10 +47,6 @@ public abstract class AbstractGroupingDeviceBusElement<TProvider extends IForgeR
///////////////////////////////////////////////////////////////////
public Collection<TDeviceInfo> getDeviceGroup(final int index) {
return groups.get(index);
}
public CompoundTag save() {
final ListTag listTag = new ListTag();
for (int i = 0; i < groupCount; i++) {
@@ -83,9 +84,9 @@ public abstract class AbstractGroupingDeviceBusElement<TProvider extends IForgeR
@Override
public Optional<UUID> getDeviceIdentifier(final Device device) {
for (int i = 0; i < groupCount; i++) {
final HashSet<TDeviceInfo> group = groups.get(i);
for (final TDeviceInfo deviceInfo : group) {
if (Objects.equals(device, deviceInfo.device)) {
final HashSet<T> group = groups.get(i);
for (final T deviceInfo : group) {
if (Objects.equals(device, deviceInfo.getDevice())) {
return Optional.of(groupIds[i]);
}
}
@@ -95,35 +96,37 @@ public abstract class AbstractGroupingDeviceBusElement<TProvider extends IForgeR
///////////////////////////////////////////////////////////////////
protected final void setDevicesForGroup(final int index, final Set<TDeviceInfo> newDevices) {
final HashSet<TDeviceInfo> oldDevices = groups.get(index);
if (Objects.equals(newDevices, oldDevices)) {
protected final void setEntriesForGroup(final int index, final Set<T> newEntries) {
final HashSet<T> oldEntries = groups.get(index);
if (Objects.equals(newEntries, oldEntries)) {
return;
}
final HashSet<TDeviceInfo> removedDevices = new HashSet<>(oldDevices);
removedDevices.removeAll(newDevices);
for (final TDeviceInfo info : removedDevices) {
devices.removeInt(info.device);
final HashSet<T> removedEntries = new HashSet<>(oldEntries);
removedEntries.removeAll(newEntries);
for (final T entry : removedEntries) {
devices.removeInt(entry.getDevice());
onEntryRemoved(entry);
}
final HashSet<TDeviceInfo> addedDevices = new HashSet<>(newDevices);
addedDevices.removeAll(oldDevices);
for (final TDeviceInfo info : addedDevices) {
devices.put(info.device, info.getEnergyConsumption());
final HashSet<T> addedEntries = new HashSet<>(newEntries);
addedEntries.removeAll(oldEntries);
for (final T entry : addedEntries) {
devices.put(entry.getDevice(), entry.getDeviceEnergyConsumption().orElse(0));
onEntryAdded(entry);
}
oldDevices.removeAll(removedDevices);
oldDevices.addAll(newDevices);
oldEntries.removeAll(removedEntries);
oldEntries.addAll(newEntries);
final CompoundTag devicesTag = groupData[index];
for (final TDeviceInfo deviceInfo : removedDevices) {
ItemDeviceUtils.getItemDeviceDataKey(deviceInfo.provider).ifPresent(devicesTag::remove);
for (final T entry : removedEntries) {
entry.getDeviceDataKey().ifPresent(devicesTag::remove);
}
for (final TDeviceInfo deviceInfo : addedDevices) {
ItemDeviceUtils.getItemDeviceDataKey(deviceInfo.provider).ifPresent(key -> {
for (final T entry : addedEntries) {
entry.getDeviceDataKey().ifPresent(key -> {
if (devicesTag.contains(key, NBTTagIds.TAG_COMPOUND)) {
deviceInfo.device.deserializeNBT(devicesTag.getCompound(key));
entry.getDevice().deserializeNBT(devicesTag.getCompound(key));
}
});
}
@@ -131,13 +134,19 @@ public abstract class AbstractGroupingDeviceBusElement<TProvider extends IForgeR
scanDevices();
}
protected void onEntryRemoved(final T entry) {
}
protected void onEntryAdded(final T entry) {
}
///////////////////////////////////////////////////////////////////
private void saveGroup(final int index) {
final CompoundTag devicesTag = new CompoundTag();
for (final TDeviceInfo deviceInfo : groups.get(index)) {
ItemDeviceUtils.getItemDeviceDataKey(deviceInfo.provider).ifPresent(key -> {
final CompoundTag deviceTag = deviceInfo.device.serializeNBT();
for (final T entry : groups.get(index)) {
entry.getDeviceDataKey().ifPresent(key -> {
final CompoundTag deviceTag = entry.getDevice().serializeNBT();
if (!deviceTag.isEmpty()) {
devicesTag.put(key, deviceTag);
}

View File

@@ -1,10 +0,0 @@
package li.cil.oc2.common.bus;
import li.cil.oc2.api.bus.device.provider.ItemDeviceProvider;
import li.cil.oc2.common.bus.device.util.ItemDeviceInfo;
public abstract class AbstractGroupingItemDeviceBusElement extends AbstractGroupingDeviceBusElement<ItemDeviceProvider, ItemDeviceInfo> {
public AbstractGroupingItemDeviceBusElement(final int groupCount) {
super(groupCount);
}
}

View File

@@ -3,7 +3,9 @@ package li.cil.oc2.common.bus;
import li.cil.oc2.api.bus.BlockDeviceBusElement;
import li.cil.oc2.api.bus.DeviceBus;
import li.cil.oc2.api.bus.DeviceBusElement;
import li.cil.oc2.api.bus.device.Device;
import li.cil.oc2.api.bus.device.provider.BlockDeviceQuery;
import li.cil.oc2.api.util.Invalidatable;
import li.cil.oc2.common.Constants;
import li.cil.oc2.common.bus.device.rpc.TypeNameRPCDevice;
import li.cil.oc2.common.bus.device.util.BlockDeviceInfo;
@@ -20,14 +22,12 @@ import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraftforge.common.util.LazyOptional;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Optional;
import java.util.*;
import static java.util.Objects.requireNonNull;
import static li.cil.oc2.common.util.RegistryUtils.optionalKey;
public class BlockEntityDeviceBusElement extends AbstractGroupingBlockDeviceBusElement implements BlockDeviceBusElement {
public class BlockEntityDeviceBusElement extends AbstractGroupingDeviceBusElement<BlockEntityDeviceBusElement.BlockEntry> implements BlockDeviceBusElement {
private final BlockEntity blockEntity;
///////////////////////////////////////////////////////////////////
@@ -86,7 +86,7 @@ public class BlockEntityDeviceBusElement extends AbstractGroupingBlockDeviceBusE
public void handleNeighborChanged(final BlockPos pos) {
final Level level = blockEntity.getLevel();
if (level == null || level.isClientSide()) {
if (level == null || level.isClientSide() || !level.isLoaded(pos)) {
return;
}
@@ -96,10 +96,10 @@ public class BlockEntityDeviceBusElement extends AbstractGroupingBlockDeviceBusE
return;
}
final HashSet<BlockDeviceInfo> newDevices = collectDevices(level, pos, direction);
final HashSet<BlockEntry> newDevices = collectDevices(level, pos, direction);
final int index = direction.get3DDataValue();
setDevicesForGroup(index, newDevices);
setEntriesForGroup(index, newDevices);
}
public void initialize() {
@@ -124,28 +124,41 @@ public class BlockEntityDeviceBusElement extends AbstractGroupingBlockDeviceBusE
return canScanContinueTowards(direction);
}
protected HashSet<BlockDeviceInfo> collectDevices(final Level level, final BlockPos pos, @Nullable final Direction direction) {
final HashSet<BlockDeviceInfo> newDevices = new HashSet<>();
protected HashSet<BlockEntry> collectDevices(final Level level, final BlockPos pos, @Nullable final Direction direction) {
final HashSet<BlockEntry> entries = new HashSet<>();
if (canDetectDevicesTowards(direction)) {
final BlockDeviceQuery query = Devices.makeQuery(level, pos, direction);
for (final LazyOptional<BlockDeviceInfo> deviceInfo : Devices.getDevices(query)) {
deviceInfo.ifPresent(newDevices::add);
deviceInfo.addListener(unused -> handleNeighborChanged(pos));
for (final Invalidatable<BlockDeviceInfo> deviceInfo : Devices.getDevices(query)) {
if (deviceInfo.isPresent()) {
entries.add(new BlockEntry(deviceInfo, pos));
}
}
}
collectSyntheticDevices(level, pos, direction, newDevices);
collectSyntheticDevices(level, pos, direction, entries);
return newDevices;
return entries;
}
protected void collectSyntheticDevices(final Level level, final BlockPos pos, @Nullable final Direction direction, final HashSet<BlockDeviceInfo> devices) {
protected void collectSyntheticDevices(final Level level, final BlockPos pos, @Nullable final Direction direction, final HashSet<BlockEntry> entries) {
final String blockName = LevelUtils.getBlockName(level, pos);
if (blockName != null) {
devices.add(new BlockDeviceInfo(null, new TypeNameRPCDevice(blockName)));
entries.add(new BlockEntry(new BlockDeviceInfo(null, new TypeNameRPCDevice(blockName)), pos));
}
}
@Override
protected void onEntryAdded(final BlockEntry entry) {
super.onEntryAdded(entry);
entry.addListener();
}
@Override
protected void onEntryRemoved(final BlockEntry entry) {
super.onEntryRemoved(entry);
entry.removeListener();
}
///////////////////////////////////////////////////////////////////
private void scanNeighborsForDevices() {
@@ -169,4 +182,56 @@ public class BlockEntityDeviceBusElement extends AbstractGroupingBlockDeviceBusE
capability.ifPresent(DeviceBus::scheduleScan);
}
}
///////////////////////////////////////////////////////////////////
protected final class BlockEntry implements Entry {
private final Invalidatable<BlockDeviceInfo> deviceInfo;
@Nullable private final String dataKey;
private final Device device;
private final BlockPos pos;
private Invalidatable.ListenerToken token;
public BlockEntry(final Invalidatable<BlockDeviceInfo> deviceInfo, final BlockPos pos) {
this.deviceInfo = deviceInfo;
this.pos = pos;
// Grab these while the device info has not yet been invalidated. We still need to access
// these even after the device has been invalidated to clean up.
this.dataKey = optionalKey(deviceInfo.get().provider).orElse(null);
this.device = deviceInfo.get().device;
}
public BlockEntry(final BlockDeviceInfo deviceInfo, final BlockPos pos) {
this(Invalidatable.of(deviceInfo), pos);
}
@Override
public Optional<String> getDeviceDataKey() {
return Optional.ofNullable(dataKey);
}
@Override
public OptionalInt getDeviceEnergyConsumption() {
return deviceInfo.isPresent() ? OptionalInt.of(deviceInfo.get().getEnergyConsumption()) : OptionalInt.empty();
}
@Override
public Device getDevice() {
return device;
}
public void addListener() {
if (token == null) {
token = deviceInfo.addListener(unused -> handleNeighborChanged(pos));
}
}
public void removeListener() {
if (token != null) {
token.removeListener();
token = null;
}
}
}
}

View File

@@ -5,6 +5,7 @@ import li.cil.oc2.api.bus.DeviceBusElement;
import li.cil.oc2.api.bus.device.Device;
import li.cil.oc2.common.Constants;
import li.cil.oc2.common.util.Event;
import li.cil.oc2.common.util.LazyOptionalUtils;
import li.cil.oc2.common.util.ParameterizedEvent;
import net.minecraftforge.common.util.LazyOptional;
@@ -207,7 +208,9 @@ public class CommonDeviceBusController implements DeviceBusController {
// Rescan if any bus element gets invalidated.
for (final LazyOptional<DeviceBusElement> optional : optionals) {
assert optional.isPresent();
optional.addListener(unused -> scheduleBusScan());
// Don't have bus elements keep this instance alive, only notify us on change if we still exist.
LazyOptionalUtils.addWeakListener(optional, this, (controller, unused) -> controller.scheduleBusScan());
}
onAfterBusScan();

View File

@@ -1,5 +1,7 @@
package li.cil.oc2.common.bus;
import li.cil.oc2.api.bus.device.Device;
import li.cil.oc2.api.bus.device.ItemDevice;
import li.cil.oc2.api.bus.device.provider.ItemDeviceQuery;
import li.cil.oc2.api.bus.device.rpc.RPCDevice;
import li.cil.oc2.common.bus.device.rpc.TypeNameRPCDevice;
@@ -11,11 +13,12 @@ import net.minecraft.nbt.CompoundTag;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack;
import java.util.Collections;
import java.util.HashSet;
import java.util.*;
import java.util.function.Function;
public final class ItemHandlerDeviceBusElement extends AbstractGroupingItemDeviceBusElement {
import static li.cil.oc2.common.util.RegistryUtils.optionalKey;
public final class ItemHandlerDeviceBusElement extends AbstractGroupingDeviceBusElement<ItemHandlerDeviceBusElement.ItemEntry> {
private final Function<ItemStack, ItemDeviceQuery> queryFactory;
public ItemHandlerDeviceBusElement(final int slotCount, final Function<ItemStack, ItemDeviceQuery> queryFactory) {
@@ -25,15 +28,25 @@ public final class ItemHandlerDeviceBusElement extends AbstractGroupingItemDevic
///////////////////////////////////////////////////////////////////
public boolean groupContains(final int groupIndex, final Device device) {
for (final ItemEntry entry : groups.get(groupIndex)) {
if (Objects.equals(entry.getDevice(), device)) {
return true;
}
}
return false;
}
public void updateDevices(final int slot, final ItemStack stack) {
if (!stack.isEmpty()) {
final ItemDeviceQuery query = queryFactory.apply(stack);
final HashSet<ItemDeviceInfo> newDevices = new HashSet<>(Devices.getDevices(query));
final HashSet<ItemEntry> newDevices = new HashSet<>(Devices.getDevices(query).stream().map(ItemEntry::new).toList());
insertItemNameDevice(stack, newDevices);
importDeviceDataFromItemStack(stack, newDevices);
setDevicesForGroup(slot, newDevices);
setEntriesForGroup(slot, newDevices);
} else {
setDevicesForGroup(slot, Collections.emptySet());
setEntriesForGroup(slot, Collections.emptySet());
}
}
@@ -43,10 +56,10 @@ public final class ItemHandlerDeviceBusElement extends AbstractGroupingItemDevic
}
final CompoundTag exportedTag = new CompoundTag();
for (final ItemDeviceInfo info : groups.get(slot)) {
ItemDeviceUtils.getItemDeviceDataKey(info.provider).ifPresent(key -> {
for (final ItemEntry entry : groups.get(slot)) {
entry.getDeviceDataKey().ifPresent(key -> {
final CompoundTag deviceTag = new CompoundTag();
info.device.exportToItemStack(deviceTag);
entry.getDevice().exportToItemStack(deviceTag);
if (!deviceTag.isEmpty()) {
exportedTag.put(key, deviceTag);
}
@@ -60,25 +73,44 @@ public final class ItemHandlerDeviceBusElement extends AbstractGroupingItemDevic
///////////////////////////////////////////////////////////////////
private void importDeviceDataFromItemStack(final ItemStack stack, final HashSet<ItemDeviceInfo> devices) {
private void importDeviceDataFromItemStack(final ItemStack stack, final HashSet<ItemEntry> entries) {
final CompoundTag exportedTag = ItemDeviceUtils.getItemDeviceData(stack);
if (!exportedTag.isEmpty()) {
for (final ItemDeviceInfo info : devices) {
ItemDeviceUtils.getItemDeviceDataKey(info.provider).ifPresent(key -> {
for (final ItemEntry entry : entries) {
entry.getDeviceDataKey().ifPresent(key -> {
if (exportedTag.contains(key, NBTTagIds.TAG_COMPOUND)) {
info.device.importFromItemStack(exportedTag.getCompound(key));
entry.deviceInfo.device.importFromItemStack(exportedTag.getCompound(key));
}
});
}
}
}
private void insertItemNameDevice(final ItemStack stack, final HashSet<ItemDeviceInfo> devices) {
if (devices.stream().anyMatch(info -> info.device instanceof RPCDevice)) {
private void insertItemNameDevice(final ItemStack stack, final HashSet<ItemEntry> entries) {
if (entries.stream().anyMatch(entry -> entry.getDevice() instanceof RPCDevice)) {
final ResourceLocation registryName = stack.getItem().getRegistryName();
if (registryName != null) {
devices.add(new ItemDeviceInfo(null, new TypeNameRPCDevice(registryName.toString()), 0));
entries.add(new ItemEntry(new ItemDeviceInfo(null, new TypeNameRPCDevice(registryName.toString()), 0)));
}
}
}
///////////////////////////////////////////////////////////////////
protected record ItemEntry(ItemDeviceInfo deviceInfo) implements Entry {
@Override
public Optional<String> getDeviceDataKey() {
return optionalKey(deviceInfo.provider);
}
@Override
public OptionalInt getDeviceEnergyConsumption() {
return OptionalInt.of(deviceInfo.getEnergyConsumption());
}
@Override
public ItemDevice getDevice() {
return deviceInfo.device;
}
}
}

View File

@@ -4,17 +4,17 @@ import li.cil.oc2.api.bus.device.Device;
import li.cil.oc2.api.bus.device.object.Callbacks;
import li.cil.oc2.api.bus.device.object.ObjectDevice;
import li.cil.oc2.api.bus.device.provider.BlockDeviceQuery;
import li.cil.oc2.api.util.Invalidatable;
import li.cil.oc2.common.bus.device.provider.util.AbstractBlockEntityDeviceProvider;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraftforge.common.util.LazyOptional;
public final class BlockEntityDeviceProvider extends AbstractBlockEntityDeviceProvider<BlockEntity> {
@Override
public LazyOptional<Device> getBlockDevice(final BlockDeviceQuery query, final BlockEntity blockEntity) {
public Invalidatable<Device> getBlockDevice(final BlockDeviceQuery query, final BlockEntity blockEntity) {
if (Callbacks.hasMethods(blockEntity)) {
return LazyOptional.of(() -> new ObjectDevice(blockEntity));
return Invalidatable.of(new ObjectDevice(blockEntity));
} else {
return LazyOptional.empty();
return Invalidatable.empty();
}
}
}

View File

@@ -4,30 +4,30 @@ import li.cil.oc2.api.bus.device.Device;
import li.cil.oc2.api.bus.device.object.Callbacks;
import li.cil.oc2.api.bus.device.object.ObjectDevice;
import li.cil.oc2.api.bus.device.provider.BlockDeviceQuery;
import li.cil.oc2.api.util.Invalidatable;
import li.cil.oc2.common.bus.device.provider.util.AbstractBlockDeviceProvider;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraftforge.common.util.LazyOptional;
public final class BlockStateDeviceProvider extends AbstractBlockDeviceProvider {
@Override
public LazyOptional<Device> getDevice(final BlockDeviceQuery query) {
public Invalidatable<Device> getDevice(final BlockDeviceQuery query) {
final Level level = query.getLevel();
final BlockPos position = query.getQueryPosition();
final BlockState blockState = level.getBlockState(position);
if (blockState.isAir()) {
return LazyOptional.empty();
return Invalidatable.empty();
}
final Block block = blockState.getBlock();
if (!Callbacks.hasMethods(block)) {
return LazyOptional.empty();
return Invalidatable.empty();
}
return LazyOptional.of(() -> new ObjectDevice(block));
return Invalidatable.of(new ObjectDevice(block));
}
}

View File

@@ -2,11 +2,11 @@ package li.cil.oc2.common.bus.device.provider.block;
import li.cil.oc2.api.bus.device.Device;
import li.cil.oc2.api.bus.device.provider.BlockDeviceQuery;
import li.cil.oc2.api.util.Invalidatable;
import li.cil.oc2.common.blockentity.BlockEntities;
import li.cil.oc2.common.blockentity.DiskDriveBlockEntity;
import li.cil.oc2.common.bus.device.provider.util.AbstractBlockEntityDeviceProvider;
import net.minecraft.world.level.block.HorizontalDirectionalBlock;
import net.minecraftforge.common.util.LazyOptional;
public final class DiskDriveDeviceProvider extends AbstractBlockEntityDeviceProvider<DiskDriveBlockEntity> {
public DiskDriveDeviceProvider() {
@@ -14,13 +14,13 @@ public final class DiskDriveDeviceProvider extends AbstractBlockEntityDeviceProv
}
@Override
protected LazyOptional<Device> getBlockDevice(final BlockDeviceQuery query, final DiskDriveBlockEntity blockEntity) {
protected Invalidatable<Device> getBlockDevice(final BlockDeviceQuery query, final DiskDriveBlockEntity blockEntity) {
// We only allow connecting to exactly one face of the disk drive to ensure only one
// bus (and thus, one VM) will access the device at any single time.
if (query.getQuerySide() != blockEntity.getBlockState().getValue(HorizontalDirectionalBlock.FACING)) {
return LazyOptional.empty();
return Invalidatable.empty();
}
return LazyOptional.of(blockEntity::getDevice);
return Invalidatable.of(blockEntity.getDevice());
}
}

View File

@@ -4,11 +4,11 @@ import li.cil.oc2.api.bus.device.Device;
import li.cil.oc2.api.bus.device.object.Callback;
import li.cil.oc2.api.bus.device.object.ObjectDevice;
import li.cil.oc2.api.bus.device.provider.BlockDeviceQuery;
import li.cil.oc2.api.util.Invalidatable;
import li.cil.oc2.common.bus.device.provider.util.AbstractBlockEntityCapabilityDeviceProvider;
import li.cil.oc2.common.bus.device.util.IdentityProxy;
import li.cil.oc2.common.capabilities.Capabilities;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.energy.IEnergyStorage;
public final class EnergyStorageBlockDeviceProvider extends AbstractBlockEntityCapabilityDeviceProvider<IEnergyStorage, BlockEntity> {
@@ -19,8 +19,8 @@ public final class EnergyStorageBlockDeviceProvider extends AbstractBlockEntityC
///////////////////////////////////////////////////////////////////
@Override
protected LazyOptional<Device> getBlockDevice(final BlockDeviceQuery query, final IEnergyStorage value) {
return LazyOptional.of(() -> new ObjectDevice(new EnergyStorageDevice(value), "energy_storage"));
protected Invalidatable<Device> getBlockDevice(final BlockDeviceQuery query, final IEnergyStorage value) {
return Invalidatable.of(new ObjectDevice(new EnergyStorageDevice(value), "energy_storage"));
}
///////////////////////////////////////////////////////////////////

View File

@@ -4,11 +4,11 @@ import li.cil.oc2.api.bus.device.Device;
import li.cil.oc2.api.bus.device.object.Callback;
import li.cil.oc2.api.bus.device.object.ObjectDevice;
import li.cil.oc2.api.bus.device.provider.BlockDeviceQuery;
import li.cil.oc2.api.util.Invalidatable;
import li.cil.oc2.common.bus.device.provider.util.AbstractBlockEntityCapabilityDeviceProvider;
import li.cil.oc2.common.bus.device.util.IdentityProxy;
import li.cil.oc2.common.capabilities.Capabilities;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fluids.capability.IFluidHandler;
@@ -20,8 +20,8 @@ public final class FluidHandlerBlockDeviceProvider extends AbstractBlockEntityCa
///////////////////////////////////////////////////////////////////
@Override
protected LazyOptional<Device> getBlockDevice(final BlockDeviceQuery query, final IFluidHandler value) {
return LazyOptional.of(() -> new ObjectDevice(new FluidHandlerDevice(value), "fluid_handler"));
protected Invalidatable<Device> getBlockDevice(final BlockDeviceQuery query, final IFluidHandler value) {
return Invalidatable.of(new ObjectDevice(new FluidHandlerDevice(value), "fluid_handler"));
}
///////////////////////////////////////////////////////////////////

View File

@@ -4,12 +4,12 @@ import li.cil.oc2.api.bus.device.Device;
import li.cil.oc2.api.bus.device.object.Callback;
import li.cil.oc2.api.bus.device.object.ObjectDevice;
import li.cil.oc2.api.bus.device.provider.BlockDeviceQuery;
import li.cil.oc2.api.util.Invalidatable;
import li.cil.oc2.common.bus.device.provider.util.AbstractBlockEntityCapabilityDeviceProvider;
import li.cil.oc2.common.bus.device.util.IdentityProxy;
import li.cil.oc2.common.capabilities.Capabilities;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.items.IItemHandler;
public final class ItemHandlerBlockDeviceProvider extends AbstractBlockEntityCapabilityDeviceProvider<IItemHandler, BlockEntity> {
@@ -20,8 +20,8 @@ public final class ItemHandlerBlockDeviceProvider extends AbstractBlockEntityCap
///////////////////////////////////////////////////////////////////
@Override
protected LazyOptional<Device> getBlockDevice(final BlockDeviceQuery query, final IItemHandler value) {
return LazyOptional.of(() -> new ObjectDevice(new ItemHandlerDevice(value), "item_handler"));
protected Invalidatable<Device> getBlockDevice(final BlockDeviceQuery query, final IItemHandler value) {
return Invalidatable.of(new ObjectDevice(new ItemHandlerDevice(value), "item_handler"));
}
///////////////////////////////////////////////////////////////////

View File

@@ -2,6 +2,8 @@ package li.cil.oc2.common.bus.device.provider.util;
import li.cil.oc2.api.bus.device.Device;
import li.cil.oc2.api.bus.device.provider.BlockDeviceQuery;
import li.cil.oc2.api.util.Invalidatable;
import li.cil.oc2.common.util.LazyOptionalUtils;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraftforge.common.capabilities.Capability;
@@ -26,19 +28,22 @@ public abstract class AbstractBlockEntityCapabilityDeviceProvider<TCapability, T
///////////////////////////////////////////////////////////////////
@Override
protected final LazyOptional<Device> getBlockDevice(final BlockDeviceQuery blockQuery, final BlockEntity blockEntity) {
protected final Invalidatable<Device> getBlockDevice(final BlockDeviceQuery blockQuery, final BlockEntity blockEntity) {
final Capability<TCapability> capability = capabilitySupplier.get();
if (capability == null) throw new IllegalStateException();
final LazyOptional<TCapability> optional = blockEntity.getCapability(capability, blockQuery.getQuerySide());
if (!optional.isPresent()) {
return LazyOptional.empty();
return Invalidatable.empty();
}
final TCapability value = optional.orElseThrow(AssertionError::new);
final LazyOptional<Device> device = getBlockDevice(blockQuery, value);
optional.addListener(ignored -> device.invalidate());
final Invalidatable<Device> device = getBlockDevice(blockQuery, value);
// When capability gets invalidated, invalidate device. But don't keep device alive via capability.
LazyOptionalUtils.addWeakListener(optional, device, (invalidatable, unused) -> invalidatable.invalidate());
return device;
}
protected abstract LazyOptional<Device> getBlockDevice(final BlockDeviceQuery query, final TCapability value);
protected abstract Invalidatable<Device> getBlockDevice(final BlockDeviceQuery query, final TCapability value);
}

View File

@@ -2,10 +2,9 @@ package li.cil.oc2.common.bus.device.provider.util;
import li.cil.oc2.api.bus.device.Device;
import li.cil.oc2.api.bus.device.provider.BlockDeviceQuery;
import li.cil.oc2.common.util.LevelUtils;
import li.cil.oc2.api.util.Invalidatable;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraftforge.common.util.LazyOptional;
public abstract class AbstractBlockEntityDeviceProvider<T extends BlockEntity> extends AbstractBlockDeviceProvider {
private final BlockEntityType<T> blockEntityType;
@@ -24,14 +23,14 @@ public abstract class AbstractBlockEntityDeviceProvider<T extends BlockEntity> e
@SuppressWarnings("unchecked")
@Override
public final LazyOptional<Device> getDevice(final BlockDeviceQuery query) {
final BlockEntity blockEntity = LevelUtils.getBlockEntityIfChunkExists(query.getLevel(), query.getQueryPosition());
public final Invalidatable<Device> getDevice(final BlockDeviceQuery query) {
final BlockEntity blockEntity = query.getLevel().getBlockEntity(query.getQueryPosition());
if (blockEntity == null) {
return LazyOptional.empty();
return Invalidatable.empty();
}
if (blockEntityType != null && blockEntity.getType() != blockEntityType) {
return LazyOptional.empty();
return Invalidatable.empty();
}
return getBlockDevice(query, (T) blockEntity);
@@ -39,5 +38,5 @@ public abstract class AbstractBlockEntityDeviceProvider<T extends BlockEntity> e
///////////////////////////////////////////////////////////////////
protected abstract LazyOptional<Device> getBlockDevice(final BlockDeviceQuery query, final T blockEntity);
protected abstract Invalidatable<Device> getBlockDevice(final BlockDeviceQuery query, final T blockEntity);
}

View File

@@ -7,6 +7,7 @@ import li.cil.oc2.api.bus.device.provider.BlockDeviceProvider;
import li.cil.oc2.api.bus.device.provider.BlockDeviceQuery;
import li.cil.oc2.api.bus.device.provider.ItemDeviceProvider;
import li.cil.oc2.api.bus.device.provider.ItemDeviceQuery;
import li.cil.oc2.api.util.Invalidatable;
import li.cil.oc2.common.bus.device.provider.Providers;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
@@ -14,7 +15,6 @@ import net.minecraft.world.entity.Entity;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.registries.IForgeRegistry;
import javax.annotation.Nullable;
@@ -45,19 +45,17 @@ public final class Devices {
return new ItemQuery(entity, stack);
}
public static List<LazyOptional<BlockDeviceInfo>> getDevices(final BlockDeviceQuery query) {
public static List<Invalidatable<BlockDeviceInfo>> getDevices(final BlockDeviceQuery query) {
if (!query.getLevel().isLoaded(query.getQueryPosition())) {
return Collections.emptyList();
}
final IForgeRegistry<BlockDeviceProvider> registry = Providers.BLOCK_DEVICE_PROVIDER_REGISTRY.get();
final ArrayList<LazyOptional<BlockDeviceInfo>> devices = new ArrayList<>();
final ArrayList<Invalidatable<BlockDeviceInfo>> devices = new ArrayList<>();
for (final BlockDeviceProvider provider : registry.getValues()) {
final LazyOptional<Device> device = provider.getDevice(query);
final Invalidatable<Device> device = provider.getDevice(query);
if (device.isPresent()) {
final LazyOptional<BlockDeviceInfo> info = device.lazyMap(d -> new BlockDeviceInfo(provider, d));
device.addListener(unused -> info.invalidate());
devices.add(info);
devices.add(device.mapWithDependency(d -> new BlockDeviceInfo(provider, d)));
}
}
return devices;

View File

@@ -37,7 +37,7 @@ public final class JsonArraySerializer implements Serializer<JsonArray> {
array.remove(array.size() - 1);
}
array.addAll(new JsonParser().parse(jsonString).getAsJsonArray());
array.addAll(JsonParser.parseString(jsonString).getAsJsonArray());
return array;
}

View File

@@ -1,12 +1,7 @@
package li.cil.oc2.common.util;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack;
import net.minecraftforge.registries.IForgeRegistryEntry;
import javax.annotation.Nullable;
import java.util.Optional;
public final class ItemDeviceUtils {
private static final String ITEM_DEVICE_DATA_TAG_NAME = "item_device";
@@ -20,17 +15,4 @@ public final class ItemDeviceUtils {
public static void setItemDeviceData(final ItemStack stack, final CompoundTag data) {
ItemStackUtils.getOrCreateModDataTag(stack).put(ITEM_DEVICE_DATA_TAG_NAME, data);
}
public static Optional<String> getItemDeviceDataKey(@Nullable final IForgeRegistryEntry<?> provider) {
if (provider == null) {
return Optional.empty();
}
final ResourceLocation providerName = provider.getRegistryName();
if (providerName == null) {
return Optional.empty();
}
return Optional.of(providerName.toString());
}
}

View File

@@ -0,0 +1,36 @@
package li.cil.oc2.common.util;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.common.util.NonNullConsumer;
import java.lang.ref.WeakReference;
import java.util.function.BiConsumer;
public final class LazyOptionalUtils {
/**
* Adds a <em>weak</em> listener to a {@link LazyOptional}.
* <p>
* {@link LazyOptional}s do not allow removing listeners again, so once added, they will prevent garbage collection
* of listeners until they go out of scope themselves. This may result in undesired "memory leaks". To work around
* this, we register a light-weight proxy listener instead, which only keeps a weak reference to the actual
* listener implementation context.
*
* @param optional the optional to add the listener to.
* @param weakValue the value that should only be referenced weakly, allowing garbage collection.
* @param listener the listener proxy which will take the weak value as context, if it still exists.
* @param <T> the type of the optional.
* @param <U> the type of the listener context.
*/
public static <T, U> void addWeakListener(final LazyOptional<T> optional, final U weakValue, final BiConsumer<U, LazyOptional<T>> listener) {
optional.addListener(buildListener(new WeakReference<U>(weakValue), listener));
}
private static <T, U> NonNullConsumer<LazyOptional<T>> buildListener(final WeakReference<U> weakValue, final BiConsumer<U, LazyOptional<T>> listener) {
return capability -> {
final U value = weakValue.get();
if (value != null) {
listener.accept(value, capability);
}
};
}
}

View File

@@ -1,14 +1,17 @@
package li.cil.oc2.common.util;
import li.cil.oc2.api.API;
import net.minecraft.resources.ResourceLocation;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
import net.minecraftforge.registries.DeferredRegister;
import net.minecraftforge.registries.IForgeRegistry;
import net.minecraftforge.registries.IForgeRegistryEntry;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
public abstract class RegistryUtils {
private enum Phase {
@@ -56,6 +59,19 @@ public abstract class RegistryUtils {
return Objects.requireNonNull(registryEntry.getRegistryName()).toString();
}
public static <T> Optional<String> optionalKey(@Nullable final IForgeRegistryEntry<T> registryEntry) {
if (registryEntry == null) {
return Optional.empty();
}
final ResourceLocation providerName = registryEntry.getRegistryName();
if (providerName == null) {
return Optional.empty();
}
return Optional.of(providerName.toString());
}
private RegistryUtils() {
}
}

View File

@@ -6,7 +6,6 @@ import li.cil.oc2.api.bus.device.DeviceTypes;
import li.cil.oc2.api.bus.device.provider.ItemDeviceQuery;
import li.cil.oc2.api.bus.device.vm.VMDevice;
import li.cil.oc2.common.bus.AbstractDeviceBusElement;
import li.cil.oc2.common.bus.device.util.ItemDeviceInfo;
import li.cil.oc2.common.container.DeviceItemStackHandler;
import li.cil.oc2.common.container.TypedDeviceItemStackHandler;
import net.minecraft.nbt.CompoundTag;
@@ -76,17 +75,14 @@ public abstract class AbstractVMItemStackHandlers implements VMItemStackHandlers
final DeviceItemStackHandler handler = entry.getValue();
for (int i = 0; i < handler.getSlots(); i++) {
final Collection<ItemDeviceInfo> devices = handler.getBusElement().getDeviceGroup(i);
for (final ItemDeviceInfo info : devices) {
if (Objects.equals(info.device, wrapper)) {
// Ahhh, such special casing, much wow. Honestly I don't expect this
// special case to ever be needed for anything other than physical
// memory, so it's fine. Prove me wrong.
if (deviceType == DeviceTypes.MEMORY) {
return OptionalLong.empty();
} else {
return OptionalLong.of(address);
}
if (handler.getBusElement().groupContains(i, wrapper)) {
// Ahhh, such special casing, much wow. Honestly I don't expect this
// special case to ever be needed for anything other than physical
// memory, so it's fine. Prove me wrong.
if (deviceType == DeviceTypes.MEMORY) {
return OptionalLong.empty();
} else {
return OptionalLong.of(address);
}
}