Put some more effort into avoiding listeners keeping references to things that may otherwise be garbage collected.
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
107
src/main/java/li/cil/oc2/api/util/Invalidatable.java
Normal file
107
src/main/java/li/cil/oc2/api/util/Invalidatable.java
Normal 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 () -> {
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"));
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -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"));
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -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"));
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
36
src/main/java/li/cil/oc2/common/util/LazyOptionalUtils.java
Normal file
36
src/main/java/li/cil/oc2/common/util/LazyOptionalUtils.java
Normal 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);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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() {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user