diff --git a/src/main/java/li/cil/oc2/Constants.java b/src/main/java/li/cil/oc2/Constants.java index b3c7d520..20777e83 100644 --- a/src/main/java/li/cil/oc2/Constants.java +++ b/src/main/java/li/cil/oc2/Constants.java @@ -2,6 +2,7 @@ package li.cil.oc2; public final class Constants { public static final String COMPUTER_BLOCK_NAME = "computer"; + public static final String BUS_CABLE_BLOCK_NAME = "bus_cable"; public static final String REDSTONE_INTERFACE_BLOCK_NAME = "redstone_interface"; public static final String SCREEN_BLOCK_NAME = "screen"; } diff --git a/src/main/java/li/cil/oc2/OpenComputers.java b/src/main/java/li/cil/oc2/OpenComputers.java index 775c8b97..b82938fa 100644 --- a/src/main/java/li/cil/oc2/OpenComputers.java +++ b/src/main/java/li/cil/oc2/OpenComputers.java @@ -4,10 +4,12 @@ import li.cil.ceres.Ceres; import li.cil.oc2.api.API; import li.cil.oc2.client.ClientSetup; import li.cil.oc2.common.CommonSetup; +import li.cil.oc2.common.block.BusCableBlock; import li.cil.oc2.common.block.ComputerBlock; import li.cil.oc2.common.block.RedstoneInterfaceBlock; import li.cil.oc2.common.block.ScreenBlock; import li.cil.oc2.common.container.ComputerContainer; +import li.cil.oc2.common.tile.BusCableTileEntity; import li.cil.oc2.common.tile.ComputerTileEntity; import li.cil.sedna.devicetree.DeviceTreeRegistry; import net.minecraft.block.Block; @@ -37,16 +39,19 @@ public final class OpenComputers { public static final DeferredRegister BLOCKS = DeferredRegister.create(ForgeRegistries.BLOCKS, API.MOD_ID); public static final RegistryObject COMPUTER_BLOCK = BLOCKS.register(Constants.COMPUTER_BLOCK_NAME, ComputerBlock::new); + public static final RegistryObject BUS_CABLE_BLOCK = BLOCKS.register(Constants.BUS_CABLE_BLOCK_NAME, BusCableBlock::new); public static final RegistryObject REDSTONE_INTERFACE_BLOCK = BLOCKS.register(Constants.REDSTONE_INTERFACE_BLOCK_NAME, RedstoneInterfaceBlock::new); public static final RegistryObject SCREEN_BLOCK = BLOCKS.register(Constants.SCREEN_BLOCK_NAME, ScreenBlock::new); public static final DeferredRegister ITEMS = DeferredRegister.create(ForgeRegistries.ITEMS, API.MOD_ID); public static final RegistryObject COMPUTER_ITEM = ITEMS.register(Constants.COMPUTER_BLOCK_NAME, () -> new BlockItem(COMPUTER_BLOCK.get(), new Item.Properties().group(ITEM_GROUP))); + public static final RegistryObject BUS_CABLE_ITEM = ITEMS.register(Constants.BUS_CABLE_BLOCK_NAME, () -> new BlockItem(BUS_CABLE_BLOCK.get(), new Item.Properties().group(ITEM_GROUP))); public static final RegistryObject REDSTONE_INTERFACE_ITEM = ITEMS.register(Constants.REDSTONE_INTERFACE_BLOCK_NAME, () -> new BlockItem(REDSTONE_INTERFACE_BLOCK.get(), new Item.Properties().group(ITEM_GROUP))); public static final RegistryObject SCREEN_ITEM = ITEMS.register(Constants.SCREEN_BLOCK_NAME, () -> new BlockItem(SCREEN_BLOCK.get(), new Item.Properties().group(ITEM_GROUP))); public static final DeferredRegister> TILES = DeferredRegister.create(ForgeRegistries.TILE_ENTITIES, API.MOD_ID); public static final RegistryObject> COMPUTER_TILE_ENTITY = TILES.register(Constants.COMPUTER_BLOCK_NAME, () -> TileEntityType.Builder.create(ComputerTileEntity::new, COMPUTER_BLOCK.get()).build(null)); + public static final RegistryObject> BUS_CABLE_TILE_ENTITY = TILES.register(Constants.BUS_CABLE_BLOCK_NAME, () -> TileEntityType.Builder.create(BusCableTileEntity::new, BUS_CABLE_BLOCK.get()).build(null)); public static final DeferredRegister> CONTAINERS = DeferredRegister.create(ForgeRegistries.CONTAINERS, API.MOD_ID); public static final RegistryObject> COMPUTER_CONTAINER = CONTAINERS.register(Constants.COMPUTER_BLOCK_NAME, () -> IForgeContainerType.create((id, inventory, data) -> { diff --git a/src/main/java/li/cil/oc2/common/CommonSetup.java b/src/main/java/li/cil/oc2/common/CommonSetup.java index 57ee8076..b9afe7f1 100644 --- a/src/main/java/li/cil/oc2/common/CommonSetup.java +++ b/src/main/java/li/cil/oc2/common/CommonSetup.java @@ -1,6 +1,8 @@ package li.cil.oc2.common; import li.cil.oc2.common.capabilities.DeviceBusElementCapability; +import li.cil.oc2.common.device.DeviceMethodParameterTypeAdapters; +import li.cil.oc2.common.device.Providers; import li.cil.oc2.common.network.Network; import li.cil.oc2.common.vm.Allocator; import li.cil.oc2.serialization.BlobStorage; diff --git a/src/main/java/li/cil/oc2/common/block/BusCableBlock.java b/src/main/java/li/cil/oc2/common/block/BusCableBlock.java new file mode 100644 index 00000000..fab804e7 --- /dev/null +++ b/src/main/java/li/cil/oc2/common/block/BusCableBlock.java @@ -0,0 +1,41 @@ +package li.cil.oc2.common.block; + +import li.cil.oc2.OpenComputers; +import li.cil.oc2.common.tile.BusCableTileEntity; +import net.minecraft.block.Block; +import net.minecraft.block.BlockState; +import net.minecraft.block.SoundType; +import net.minecraft.block.material.Material; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.IBlockReader; +import net.minecraft.world.World; + +import javax.annotation.Nullable; + +public class BusCableBlock extends Block { + public BusCableBlock() { + super(Properties.create(Material.IRON).sound(SoundType.METAL)); + } + + @Override + public boolean hasTileEntity(final BlockState state) { + return true; + } + + @Nullable + @Override + public TileEntity createTileEntity(final BlockState state, final IBlockReader world) { + return OpenComputers.BUS_CABLE_TILE_ENTITY.get().create(); + } + + @SuppressWarnings("deprecation") + @Override + public void neighborChanged(final BlockState state, final World world, final BlockPos pos, final Block changedBlock, final BlockPos changedBlockPos, final boolean isMoving) { + final TileEntity tileEntity = world.getTileEntity(pos); + if (tileEntity instanceof BusCableTileEntity) { + final BusCableTileEntity busCable = (BusCableTileEntity) tileEntity; + busCable.handleNeighborChanged(changedBlockPos); + } + } +} diff --git a/src/main/java/li/cil/oc2/common/bus/TileEntityDeviceBusElement.java b/src/main/java/li/cil/oc2/common/bus/TileEntityDeviceBusElement.java new file mode 100644 index 00000000..3e7e634f --- /dev/null +++ b/src/main/java/li/cil/oc2/common/bus/TileEntityDeviceBusElement.java @@ -0,0 +1,161 @@ +package li.cil.oc2.common.bus; + +import li.cil.oc2.api.bus.DeviceBus; +import li.cil.oc2.api.bus.DeviceBusElement; +import li.cil.oc2.common.ServerScheduler; +import li.cil.oc2.common.device.CompoundDevice; +import li.cil.oc2.common.device.IdentifiableDeviceImpl; +import li.cil.oc2.common.device.Providers; +import li.cil.oc2.common.util.NBTTagIds; +import li.cil.oc2.common.util.TileEntityUtils; +import li.cil.oc2.common.util.WorldUtils; +import net.minecraft.nbt.CompoundNBT; +import net.minecraft.nbt.ListNBT; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.Direction; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; +import net.minecraftforge.common.util.INBTSerializable; +import net.minecraftforge.common.util.LazyOptional; + +import java.util.Objects; +import java.util.Optional; +import java.util.UUID; + +public final class TileEntityDeviceBusElement implements INBTSerializable { + private static final String DEVICE_IDS_NBT_TAG_NAME = "deviceIds"; + private static final String DEVICE_ID_NBT_TAG_NAME = "deviceId"; + + private static final int NEIGHBOR_COUNT = 6; + + private final TileEntity tileEntity; + + private final DeviceBusElementImpl busElement = new DeviceBusElementImpl(); + private final UUID[] deviceIds = new UUID[NEIGHBOR_COUNT]; + private final IdentifiableDeviceImpl[] devices = new IdentifiableDeviceImpl[NEIGHBOR_COUNT]; + + public TileEntityDeviceBusElement(final TileEntity tileEntity) { + this.tileEntity = tileEntity; + + for (int i = 0; i < NEIGHBOR_COUNT; i++) { + deviceIds[i] = UUID.randomUUID(); + } + } + + public DeviceBusElementImpl getBusElement() { + return busElement; + } + + public void handleNeighborChanged(final BlockPos pos) { + final World world = tileEntity.getWorld(); + if (world == null || world.isRemote()) { + return; + } + + final BlockPos toPos = pos.subtract(tileEntity.getPos()); + final Direction direction = Direction.byLong(toPos.getX(), toPos.getY(), toPos.getZ()); + if (direction == null) { + return; + } + + final int index = direction.getIndex(); + + final LazyOptional device = Providers.getDevice(world, pos, direction); + final IdentifiableDeviceImpl identifiableDevice; + + if (device.isPresent()) { + identifiableDevice = new IdentifiableDeviceImpl(device, deviceIds[index]); + device.addListener((ignored) -> handleNeighborChanged(pos)); + } else { + identifiableDevice = null; + } + + if (Objects.equals(devices[index], identifiableDevice)) { + return; + } + + if (devices[index] != null) { + busElement.removeDevice(devices[index]); + } + + devices[index] = identifiableDevice; + + if (devices[index] != null) { + busElement.addDevice(devices[index]); + } + } + + public void initialize() { + final World world = tileEntity.getWorld(); + if (world == null || world.isRemote()) { + return; + } + + ServerScheduler.schedule(world, () -> { + if (tileEntity.isRemoved()) return; + scanNeighborsForBusElements(tileEntity.getWorld()); + scanNeighborsForDevices(); + }); + } + + public void dispose() { + busElement.scheduleScan(); + } + + @Override + public CompoundNBT serializeNBT() { + final ListNBT deviceIdsNbt = new ListNBT(); + for (int i = 0; i < NEIGHBOR_COUNT; i++) { + final CompoundNBT deviceIdNbt = new CompoundNBT(); + deviceIdNbt.putUniqueId(DEVICE_ID_NBT_TAG_NAME, deviceIds[i]); + deviceIdsNbt.add(deviceIdNbt); + } + + final CompoundNBT compound = new CompoundNBT(); + compound.put(DEVICE_IDS_NBT_TAG_NAME, deviceIdsNbt); + return compound; + } + + @Override + public void deserializeNBT(final CompoundNBT compound) { + final ListNBT deviceIdsNbt = compound.getList(DEVICE_IDS_NBT_TAG_NAME, NBTTagIds.TAG_COMPOUND); + for (int i = 0; i < Math.min(deviceIdsNbt.size(), NEIGHBOR_COUNT); i++) { + final CompoundNBT deviceIdNbt = deviceIdsNbt.getCompound(i); + if (deviceIdNbt.hasUniqueId(DEVICE_ID_NBT_TAG_NAME)) { + deviceIds[i] = deviceIdNbt.getUniqueId(DEVICE_ID_NBT_TAG_NAME); + } + } + } + + public CompoundNBT write(final CompoundNBT compound) { + final ListNBT deviceIdsNbt = new ListNBT(); + for (int i = 0; i < NEIGHBOR_COUNT; i++) { + final CompoundNBT deviceIdNbt = new CompoundNBT(); + deviceIdNbt.putUniqueId(DEVICE_ID_NBT_TAG_NAME, deviceIds[i]); + deviceIdsNbt.add(deviceIdNbt); + } + compound.put(DEVICE_IDS_NBT_TAG_NAME, deviceIdsNbt); + + return compound; + } + + private void scanNeighborsForDevices() { + for (final Direction direction : Direction.values()) { + handleNeighborChanged(tileEntity.getPos().offset(direction)); + } + } + + private void scanNeighborsForBusElements(final World world) { + final BlockPos pos = tileEntity.getPos(); + for (final Direction direction : Direction.values()) { + final BlockPos neighborPos = pos.offset(direction); + final TileEntity tileEntity = WorldUtils.getTileEntityIfChunkExists(world, neighborPos); + if (tileEntity == null) { + continue; + } + + final Optional capability = TileEntityUtils.getInterfaceForSide(tileEntity, DeviceBusElement.class, direction.getOpposite()); + capability.ifPresent(DeviceBus::scheduleScan); + } + } +} diff --git a/src/main/java/li/cil/oc2/common/device/BlockDeviceQueryImpl.java b/src/main/java/li/cil/oc2/common/device/BlockDeviceQueryImpl.java new file mode 100644 index 00000000..e5ea8cec --- /dev/null +++ b/src/main/java/li/cil/oc2/common/device/BlockDeviceQueryImpl.java @@ -0,0 +1,36 @@ +package li.cil.oc2.common.device; + +import li.cil.oc2.api.device.provider.BlockDeviceQuery; +import net.minecraft.util.Direction; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; + +import javax.annotation.Nullable; + +public class BlockDeviceQueryImpl implements BlockDeviceQuery { + private final World world; + private final BlockPos pos; + @Nullable private final Direction side; + + public BlockDeviceQueryImpl(final World world, final BlockPos pos, @Nullable final Direction side) { + this.world = world; + this.pos = pos; + this.side = side; + } + + @Override + public World getWorld() { + return world; + } + + @Override + public BlockPos getQueryPosition() { + return pos; + } + + @Nullable + @Override + public Direction getQuerySide() { + return side; + } +} diff --git a/src/main/java/li/cil/oc2/common/device/DeviceMethodParameterTypeAdapters.java b/src/main/java/li/cil/oc2/common/device/DeviceMethodParameterTypeAdapters.java new file mode 100644 index 00000000..34891770 --- /dev/null +++ b/src/main/java/li/cil/oc2/common/device/DeviceMethodParameterTypeAdapters.java @@ -0,0 +1,29 @@ +package li.cil.oc2.common.device; + +import com.google.gson.GsonBuilder; +import li.cil.oc2.api.imc.DeviceMethodParameterTypeAdapter; + +import java.util.ArrayList; + +public final class DeviceMethodParameterTypeAdapters { + private static final ArrayList TYPE_ADAPTERS = new ArrayList<>(); + + public static void addTypeAdapter(final Class type, final Object typeAdapter) { + addTypeAdapter(new DeviceMethodParameterTypeAdapter(type, typeAdapter)); + } + + public static void addTypeAdapter(final DeviceMethodParameterTypeAdapter value) { + TYPE_ADAPTERS.add(value); + } + + public static GsonBuilder beginBuildGson() { + final GsonBuilder builder = new GsonBuilder() + .serializeNulls(); + + for (final DeviceMethodParameterTypeAdapter value : TYPE_ADAPTERS) { + builder.registerTypeAdapter(value.type, value.typeAdapter); + } + + return builder; + } +} diff --git a/src/main/java/li/cil/oc2/common/device/IdentifiableDeviceImpl.java b/src/main/java/li/cil/oc2/common/device/IdentifiableDeviceImpl.java index af0fb74b..be373e7d 100644 --- a/src/main/java/li/cil/oc2/common/device/IdentifiableDeviceImpl.java +++ b/src/main/java/li/cil/oc2/common/device/IdentifiableDeviceImpl.java @@ -3,16 +3,23 @@ package li.cil.oc2.common.device; import li.cil.oc2.api.device.Device; import li.cil.oc2.api.device.DeviceMethod; import li.cil.oc2.api.device.IdentifiableDevice; +import li.cil.oc2.common.util.LazyOptionalUtils; +import net.minecraftforge.common.util.LazyOptional; +import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.UUID; public final class IdentifiableDeviceImpl implements IdentifiableDevice { - private final Device device; + private final LazyOptional device; private final UUID uuid; public IdentifiableDeviceImpl(final Device device, final UUID uuid) { + this(LazyOptional.of(() -> device), uuid); + } + + public IdentifiableDeviceImpl(final LazyOptional device, final UUID uuid) { this.device = device; this.uuid = uuid; } @@ -24,12 +31,12 @@ public final class IdentifiableDeviceImpl implements IdentifiableDevice { @Override public List getTypeNames() { - return device.getTypeNames(); + return device.map(Device::getTypeNames).orElse(Collections.emptyList()); } @Override public List getMethods() { - return device.getMethods(); + return device.map(Device::getMethods).orElse(Collections.emptyList()); } @Override @@ -38,11 +45,11 @@ public final class IdentifiableDeviceImpl implements IdentifiableDevice { if (o == null || getClass() != o.getClass()) return false; final IdentifiableDeviceImpl that = (IdentifiableDeviceImpl) o; return uuid.equals(that.uuid) && - device.equals(that.device); + LazyOptionalUtils.equals(device, that.device); } @Override public int hashCode() { - return Objects.hash(uuid, device); + return Objects.hash(uuid, LazyOptionalUtils.hashCode(device)); } } diff --git a/src/main/java/li/cil/oc2/common/device/Providers.java b/src/main/java/li/cil/oc2/common/device/Providers.java new file mode 100644 index 00000000..7959857b --- /dev/null +++ b/src/main/java/li/cil/oc2/common/device/Providers.java @@ -0,0 +1,66 @@ +package li.cil.oc2.common.device; + +import li.cil.oc2.api.device.Device; +import li.cil.oc2.api.device.provider.DeviceProvider; +import li.cil.oc2.api.device.provider.DeviceQuery; +import li.cil.oc2.common.device.provider.EnergyStorageDeviceProvider; +import li.cil.oc2.common.device.provider.FluidHandlerDeviceProvider; +import li.cil.oc2.common.device.provider.ItemHandlerDeviceProvider; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.Direction; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; +import net.minecraftforge.common.util.LazyOptional; + +import java.util.ArrayList; + +public final class Providers { + private static final ArrayList DEVICE_PROVIDERS = new ArrayList<>(); + + public static void initialize() { + addProvider(new EnergyStorageDeviceProvider()); + addProvider(new FluidHandlerDeviceProvider()); + addProvider(new ItemHandlerDeviceProvider()); + } + + public static void addProvider(final DeviceProvider provider) { + if (!DEVICE_PROVIDERS.contains(provider)) { + DEVICE_PROVIDERS.add(provider); + } + } + + public static LazyOptional getDevice(final TileEntity tileEntity, final Direction side) { + final World world = tileEntity.getWorld(); + final BlockPos pos = tileEntity.getPos(); + + if (world == null) throw new IllegalArgumentException(); + + return getDevice(world, pos, side); + } + + public static LazyOptional getDevice(final World world, final BlockPos pos, final Direction side) { + return getDevice(new BlockDeviceQueryImpl(world, pos, side)); + } + + public static LazyOptional getDevice(final DeviceQuery query) { + final ArrayList devices = new ArrayList<>(); + final ArrayList> optionals = new ArrayList<>(); + for (final DeviceProvider provider : DEVICE_PROVIDERS) { + final LazyOptional optional = provider.getDevice(query); + optional.ifPresent((device) -> { + devices.add(device); + optionals.add(optional); + }); + } + + if (devices.isEmpty()) { + return LazyOptional.empty(); + } else { + final LazyOptional compoundOptional = LazyOptional.of(() -> new CompoundDevice(devices)); + for (final LazyOptional optional : optionals) { + optional.addListener((ignored) -> compoundOptional.invalidate()); + } + return compoundOptional; + } + } +} diff --git a/src/main/java/li/cil/oc2/common/tile/AbstractTileEntity.java b/src/main/java/li/cil/oc2/common/tile/AbstractTileEntity.java new file mode 100644 index 00000000..32ea3eb5 --- /dev/null +++ b/src/main/java/li/cil/oc2/common/tile/AbstractTileEntity.java @@ -0,0 +1,66 @@ +package li.cil.oc2.common.tile; + +import net.minecraft.tileentity.TileEntity; +import net.minecraft.tileentity.TileEntityType; +import net.minecraft.util.Direction; +import net.minecraftforge.common.capabilities.Capability; +import net.minecraftforge.common.util.LazyOptional; +import org.jetbrains.annotations.NotNull; + +import javax.annotation.Nullable; +import java.util.HashMap; + +public abstract class AbstractTileEntity extends TileEntity { + protected final HashMap, LazyOptional> capabilities = new HashMap<>(); + + protected AbstractTileEntity(final TileEntityType tileEntityType) { + super(tileEntityType); + } + + protected void addCapability(final Capability capability, final T value) { + capabilities.put(capability, LazyOptional.of(() -> value)); + } + + protected void initialize() { + } + + protected void dispose() { + } + + @NotNull + @Override + public LazyOptional getCapability(@NotNull final Capability capability, @Nullable final Direction side) { + final LazyOptional optional = capabilities.get(capability); + if (optional != null) { + return optional.cast(); + } else { + return super.getCapability(capability, side); + } + } + + @Override + public void onLoad() { + super.onLoad(); + initialize(); + } + + @Override + public void remove() { + super.remove(); + dispose(); + } + + @Override + public void onChunkUnloaded() { + invalidateCaps(); + dispose(); + } + + @Override + protected void invalidateCaps() { + super.invalidateCaps(); + for (final LazyOptional capability : capabilities.values()) { + capability.invalidate(); + } + } +} diff --git a/src/main/java/li/cil/oc2/common/tile/BusCableTileEntity.java b/src/main/java/li/cil/oc2/common/tile/BusCableTileEntity.java new file mode 100644 index 00000000..51fe337c --- /dev/null +++ b/src/main/java/li/cil/oc2/common/tile/BusCableTileEntity.java @@ -0,0 +1,49 @@ +package li.cil.oc2.common.tile; + +import li.cil.oc2.OpenComputers; +import li.cil.oc2.common.bus.TileEntityDeviceBusElement; +import li.cil.oc2.common.capabilities.Capabilities; +import net.minecraft.nbt.CompoundNBT; +import net.minecraft.util.math.BlockPos; + +public class BusCableTileEntity extends AbstractTileEntity { + private static final String BUS_ELEMENT_NBT_TAG_NAME = "busElement"; + + private final TileEntityDeviceBusElement busElement; + + public BusCableTileEntity() { + super(OpenComputers.BUS_CABLE_TILE_ENTITY.get()); + + busElement = new TileEntityDeviceBusElement(this); + addCapability(Capabilities.DEVICE_BUS_ELEMENT_CAPABILITY, busElement.getBusElement()); + } + + public void handleNeighborChanged(final BlockPos pos) { + busElement.handleNeighborChanged(pos); + } + + @Override + public void initialize() { + super.initialize(); + busElement.initialize(); + } + + @Override + protected void dispose() { + super.dispose(); + busElement.dispose(); + } + + @Override + public CompoundNBT write(CompoundNBT compound) { + compound = super.write(compound); + compound.put(BUS_ELEMENT_NBT_TAG_NAME, busElement.serializeNBT()); + return compound; + } + + @Override + public void read(final CompoundNBT compound) { + super.read(compound); + busElement.deserializeNBT(compound.getCompound(BUS_ELEMENT_NBT_TAG_NAME)); + } +} diff --git a/src/main/java/li/cil/oc2/common/util/LazyOptionalUtils.java b/src/main/java/li/cil/oc2/common/util/LazyOptionalUtils.java new file mode 100644 index 00000000..b9900b19 --- /dev/null +++ b/src/main/java/li/cil/oc2/common/util/LazyOptionalUtils.java @@ -0,0 +1,25 @@ +package li.cil.oc2.common.util; + +import net.minecraftforge.common.util.LazyOptional; + +import java.util.Objects; + +public final class LazyOptionalUtils { + public static boolean equals(final LazyOptional optionalA, final LazyOptional optionalB) { + if (optionalA.isPresent() != optionalB.isPresent()) { + return false; + } + + if (!optionalA.isPresent()) { + return true; + } + + final T1 valueA = optionalA.orElseThrow(AssertionError::new); + final T2 valueB = optionalB.orElseThrow(AssertionError::new); + return Objects.equals(valueA, valueB); + } + + public static int hashCode(final LazyOptional optional) { + return optional.map(Objects::hash).orElse(0); + } +}