diff --git a/src/main/kotlin/net/shadowfacts/phycon/block/extractor/ExtractorBlock.kt b/src/main/kotlin/net/shadowfacts/phycon/block/extractor/ExtractorBlock.kt index 8984fdc..f43424f 100644 --- a/src/main/kotlin/net/shadowfacts/phycon/block/extractor/ExtractorBlock.kt +++ b/src/main/kotlin/net/shadowfacts/phycon/block/extractor/ExtractorBlock.kt @@ -78,23 +78,4 @@ class ExtractorBlock : FaceDeviceBlock( override fun createBlockEntity(pos: BlockPos, state: BlockState) = ExtractorBlockEntity(pos, state) - override fun onPlaced(world: World, pos: BlockPos, state: BlockState, entity: LivingEntity?, stack: ItemStack) { - if (!world.isClient) { - getBlockEntity(world, pos)!!.updateInventory() - } - } - - override fun neighborUpdate( - state: BlockState, - world: World, - pos: BlockPos, - neighbor: Block, - neighborPos: BlockPos, - bl: Boolean - ) { - if (!world.isClient) { - getBlockEntity(world, pos)!!.updateInventory() - } - } - } diff --git a/src/main/kotlin/net/shadowfacts/phycon/block/extractor/ExtractorBlockEntity.kt b/src/main/kotlin/net/shadowfacts/phycon/block/extractor/ExtractorBlockEntity.kt index d5d4c20..04b1da0 100644 --- a/src/main/kotlin/net/shadowfacts/phycon/block/extractor/ExtractorBlockEntity.kt +++ b/src/main/kotlin/net/shadowfacts/phycon/block/extractor/ExtractorBlockEntity.kt @@ -1,13 +1,14 @@ package net.shadowfacts.phycon.block.extractor -import alexiil.mc.lib.attributes.SearchOptions -import alexiil.mc.lib.attributes.Simulation -import alexiil.mc.lib.attributes.item.FixedItemInv -import alexiil.mc.lib.attributes.item.ItemAttributes -import alexiil.mc.lib.attributes.item.filter.ExactItemStackFilter +import net.fabricmc.fabric.api.lookup.v1.block.BlockApiCache +import net.fabricmc.fabric.api.transfer.v1.item.ItemStorage +import net.fabricmc.fabric.api.transfer.v1.item.ItemVariant +import net.fabricmc.fabric.api.transfer.v1.storage.Storage +import net.fabricmc.fabric.api.transfer.v1.transaction.Transaction import net.minecraft.block.BlockState import net.minecraft.item.ItemStack import net.minecraft.nbt.NbtCompound +import net.minecraft.server.world.ServerWorld import net.minecraft.util.math.BlockPos import net.minecraft.util.math.Direction import net.shadowfacts.phycon.api.packet.Packet @@ -23,6 +24,8 @@ import net.shadowfacts.phycon.packet.ItemStackPacket import net.shadowfacts.phycon.packet.RemoteActivationPacket import net.shadowfacts.phycon.util.ActivationMode import net.shadowfacts.phycon.util.ClientConfigurableDevice +import net.shadowfacts.phycon.util.copyWithCount +import kotlin.math.min import kotlin.properties.Delegates /** @@ -41,20 +44,19 @@ class ExtractorBlockEntity(pos: BlockPos, state: BlockState) : private val facing: Direction get() = cachedState[FaceDeviceBlock.FACING] - private var inventory: FixedItemInv? = null + private var inventory: Pair, Direction>>? = null override val pendingInsertions = mutableListOf() override val dispatchStackTimeout = 1L override val controller = ActivationController(SLEEP_TIME, this) - fun updateInventory() { - val offsetPos = pos.offset(facing) - val option = SearchOptions.inDirection(facing) - inventory = ItemAttributes.FIXED_INV.getFirstOrNull(world, offsetPos, option) - } - - private fun getInventory(): FixedItemInv? { - if (inventory == null) updateInventory() - return inventory + private fun getInventory(): Storage? { + if (inventory == null) { + val offsetPos = pos.offset(facing) + val cachedFacedBlock = world!!.getBlockState(offsetPos) + val inventory = BlockApiCache.create(ItemStorage.SIDED, world!! as ServerWorld, offsetPos) + this.inventory = Pair(cachedFacedBlock, inventory) + } + return inventory!!.second.find(inventory!!.first, facing.opposite) } override fun handle(packet: Packet) { @@ -76,16 +78,15 @@ class ExtractorBlockEntity(pos: BlockPos, state: BlockState) : val inventory = getInventory() ?: return insertion.stack // if the inventory has changed, the old slot index is meaningless if (inventory !== insertion.inventory) return insertion.stack - val extracted = inventory.extractStack( - insertion.inventorySlot, - ExactItemStackFilter(insertion.stack), - ItemStack.EMPTY, - insertion.totalCapacity, - Simulation.ACTION - ) - if (extracted.isEmpty) return insertion.stack - // if we extracted less than expected, make sure super.finishInsertion doesn't send more than we actually have - insertion.stack = extracted + val transaction = Transaction.openOuter() + val extractedAmount = inventory.extract(ItemVariant.of(insertion.stack), insertion.stack.count.toLong(), transaction).toInt() + transaction.commit() + if (extractedAmount == 0) { + return insertion.stack + } else if (extractedAmount != insertion.stack.count) { + // if we extracted less than expected, make sure super.finishInsertion doesn't send more than we actually have + insertion.stack = insertion.stack.copyWithCount(extractedAmount) + } return super.finishInsertion(insertion) } @@ -101,20 +102,15 @@ class ExtractorBlockEntity(pos: BlockPos, state: BlockState) : override fun activate(): Boolean { val inventory = getInventory() ?: return false - for (slot in 0 until inventory.slotCount) { - val slotStack = inventory.getInvStack(slot) - if (slotStack.isEmpty) continue - val extractable = inventory.extractStack( - slot, - ExactItemStackFilter(slotStack), - ItemStack.EMPTY, - slotStack.count, - Simulation.SIMULATE - ) - if (extractable.isEmpty) continue - dispatchItemStack(extractable) { insertion -> + for (view in inventory.iterator(null)) { + if (view.amount <= 0) continue + val transaction = Transaction.openOuter() + var extractableAmount = inventory.simulateExtract(view.resource, view.amount, transaction).toInt() + transaction.close() + if (extractableAmount <= 0) continue + extractableAmount = min(extractableAmount, view.resource.item.maxCount) + dispatchItemStack(view.resource.toStack(extractableAmount)) { insertion -> insertion.inventory = inventory - insertion.inventorySlot = slot } return true } @@ -141,7 +137,7 @@ class ExtractorBlockEntity(pos: BlockPos, state: BlockState) : class PendingInsertion(stack: ItemStack, timestamp: Long) : NetworkStackDispatcher.PendingInsertion(stack, timestamp) { - lateinit var inventory: FixedItemInv + lateinit var inventory: Storage var inventorySlot by Delegates.notNull() } } diff --git a/src/main/kotlin/net/shadowfacts/phycon/block/inserter/InserterBlock.kt b/src/main/kotlin/net/shadowfacts/phycon/block/inserter/InserterBlock.kt index 9c02095..efb39cb 100644 --- a/src/main/kotlin/net/shadowfacts/phycon/block/inserter/InserterBlock.kt +++ b/src/main/kotlin/net/shadowfacts/phycon/block/inserter/InserterBlock.kt @@ -87,25 +87,6 @@ class InserterBlock : FaceDeviceBlock( override fun createBlockEntity(pos: BlockPos, state: BlockState) = InserterBlockEntity(pos, state) - override fun onPlaced(world: World, pos: BlockPos, state: BlockState, entity: LivingEntity?, stack: ItemStack) { - if (!world.isClient) { - getBlockEntity(world, pos)!!.updateInventory() - } - } - - override fun neighborUpdate( - state: BlockState, - world: World, - pos: BlockPos, - neighborBlock: Block, - neighborPos: BlockPos, - bl: Boolean - ) { - if (!world.isClient) { - getBlockEntity(world, pos)!!.updateInventory() - } - } - override fun onUse( state: BlockState, world: World, diff --git a/src/main/kotlin/net/shadowfacts/phycon/block/inserter/InserterBlockEntity.kt b/src/main/kotlin/net/shadowfacts/phycon/block/inserter/InserterBlockEntity.kt index 346644c..2db5c78 100644 --- a/src/main/kotlin/net/shadowfacts/phycon/block/inserter/InserterBlockEntity.kt +++ b/src/main/kotlin/net/shadowfacts/phycon/block/inserter/InserterBlockEntity.kt @@ -1,12 +1,14 @@ package net.shadowfacts.phycon.block.inserter -import alexiil.mc.lib.attributes.SearchOptions -import alexiil.mc.lib.attributes.Simulation -import alexiil.mc.lib.attributes.item.ItemAttributes -import alexiil.mc.lib.attributes.item.ItemInsertable +import net.fabricmc.fabric.api.lookup.v1.block.BlockApiCache +import net.fabricmc.fabric.api.transfer.v1.item.ItemStorage +import net.fabricmc.fabric.api.transfer.v1.item.ItemVariant +import net.fabricmc.fabric.api.transfer.v1.storage.Storage +import net.fabricmc.fabric.api.transfer.v1.transaction.Transaction import net.minecraft.block.BlockState import net.minecraft.item.ItemStack import net.minecraft.nbt.NbtCompound +import net.minecraft.server.world.ServerWorld import net.minecraft.util.math.BlockPos import net.minecraft.util.math.Direction import net.shadowfacts.phycon.api.packet.Packet @@ -18,10 +20,7 @@ import net.shadowfacts.phycon.component.NetworkStackProvider import net.shadowfacts.phycon.component.handleItemStack import net.shadowfacts.phycon.init.PhyBlockEntities import net.shadowfacts.phycon.packet.* -import net.shadowfacts.phycon.util.ActivationMode -import net.shadowfacts.phycon.util.ClientConfigurableDevice -import net.shadowfacts.phycon.util.GhostInv -import net.shadowfacts.phycon.util.equalsIgnoringAmount +import net.shadowfacts.phycon.util.* import kotlin.math.min /** @@ -41,7 +40,7 @@ class InserterBlockEntity(pos: BlockPos, state: BlockState) : DeviceBlockEntity( private val facing: Direction get() = cachedState[FaceDeviceBlock.FACING] - private var inventory: ItemInsertable? = null + private var inventory: Pair, Direction>>? = null private var currentRequest: PendingExtractRequest? = null var stackToExtract: ItemStack = ItemStack.EMPTY override var ghostSlotStack: ItemStack @@ -52,15 +51,14 @@ class InserterBlockEntity(pos: BlockPos, state: BlockState) : DeviceBlockEntity( var amountToExtract = 1 override val controller = ActivationController(SLEEP_TIME, this) - fun updateInventory() { - val offsetPos = pos.offset(facing) - val option = SearchOptions.inDirection(facing) - inventory = ItemAttributes.INSERTABLE.getFirstOrNull(world, offsetPos, option) - } - - private fun getInventory(): ItemInsertable? { - if (inventory == null) updateInventory() - return inventory + private fun getInventory(): Storage? { + if (inventory == null) { + val offsetPos = pos.offset(facing) + val cachedFacedBlock = world!!.getBlockState(offsetPos) + val inventory = BlockApiCache.create(ItemStorage.SIDED, world!! as ServerWorld, offsetPos) + this.inventory = Pair(cachedFacedBlock, inventory) + } + return inventory!!.second.find(inventory!!.first, facing.opposite) } override fun handle(packet: Packet) { @@ -73,11 +71,20 @@ class InserterBlockEntity(pos: BlockPos, state: BlockState) : DeviceBlockEntity( override fun doHandleItemStack(packet: ItemStackPacket): ItemStack { val inventory = getInventory() - return if (inventory != null) { - inventory.attemptInsertion(packet.stack, Simulation.ACTION) + if (inventory != null) { + val transaction = Transaction.openOuter() + val inserted = inventory.insert(ItemVariant.of(packet.stack), packet.stack.count.toLong(), transaction).toInt() + transaction.commit() + return if (inserted == 0) { + packet.stack + } else if (inserted == packet.stack.count) { + ItemStack.EMPTY + } else { + packet.stack.copyWithCount(packet.stack.count - inserted) + } } else { // no inventory, entire stack remains - packet.stack + return packet.stack } } diff --git a/src/main/kotlin/net/shadowfacts/phycon/block/netinterface/InterfaceBlock.kt b/src/main/kotlin/net/shadowfacts/phycon/block/netinterface/InterfaceBlock.kt index ed7667f..008cfa4 100644 --- a/src/main/kotlin/net/shadowfacts/phycon/block/netinterface/InterfaceBlock.kt +++ b/src/main/kotlin/net/shadowfacts/phycon/block/netinterface/InterfaceBlock.kt @@ -39,23 +39,4 @@ class InterfaceBlock : FaceDeviceBlock( override fun createBlockEntity(pos: BlockPos, state: BlockState) = InterfaceBlockEntity(pos, state) - override fun onPlaced(world: World, pos: BlockPos, state: BlockState, placer: LivingEntity?, stack: ItemStack) { - if (!world.isClient) { - getBlockEntity(world, pos)!!.updateInventory() - } - } - - override fun neighborUpdate( - state: BlockState, - world: World, - pos: BlockPos, - neighborBlock: Block, - neighborPos: BlockPos, - boolean_1: Boolean - ) { - if (!world.isClient) { - getBlockEntity(world, pos)!!.updateInventory() - } - } - } diff --git a/src/main/kotlin/net/shadowfacts/phycon/block/netinterface/InterfaceBlockEntity.kt b/src/main/kotlin/net/shadowfacts/phycon/block/netinterface/InterfaceBlockEntity.kt index 078c198..05a8d7e 100644 --- a/src/main/kotlin/net/shadowfacts/phycon/block/netinterface/InterfaceBlockEntity.kt +++ b/src/main/kotlin/net/shadowfacts/phycon/block/netinterface/InterfaceBlockEntity.kt @@ -1,12 +1,14 @@ package net.shadowfacts.phycon.block.netinterface -import alexiil.mc.lib.attributes.SearchOptions -import alexiil.mc.lib.attributes.Simulation -import alexiil.mc.lib.attributes.item.GroupedItemInv -import alexiil.mc.lib.attributes.item.ItemAttributes +import net.fabricmc.fabric.api.lookup.v1.block.BlockApiCache +import net.fabricmc.fabric.api.transfer.v1.item.ItemStorage +import net.fabricmc.fabric.api.transfer.v1.item.ItemVariant +import net.fabricmc.fabric.api.transfer.v1.storage.Storage +import net.fabricmc.fabric.api.transfer.v1.transaction.Transaction import net.minecraft.block.BlockState import net.minecraft.item.ItemStack import net.minecraft.nbt.NbtCompound +import net.minecraft.server.world.ServerWorld import net.minecraft.util.math.BlockPos import net.minecraft.util.math.Direction import net.shadowfacts.phycon.api.packet.Packet @@ -19,7 +21,7 @@ import net.shadowfacts.phycon.component.NetworkStackReceiver import net.shadowfacts.phycon.component.handleItemStack import net.shadowfacts.phycon.packet.* import net.shadowfacts.phycon.util.ClientConfigurableDevice -import java.lang.ref.WeakReference +import net.shadowfacts.phycon.util.copyWithCount import kotlin.math.min /** @@ -39,21 +41,17 @@ class InterfaceBlockEntity(pos: BlockPos, state: BlockState) : override var receiverPriority = 0 var syncPriorities = true - private var inventory: WeakReference? = null + private var cachedFacedBlock: BlockState? = null + private var inventoryCache: BlockApiCache, Direction>? = null - fun updateInventory() { - val offsetPos = pos.offset(facing) - val option = SearchOptions.inDirection(facing) - inventory = ItemAttributes.GROUPED_INV.getFirstOrNull(world, offsetPos, option).let { - WeakReference(it) + private fun getInventory(): Storage? { + if (cachedFacedBlock == null) { + cachedFacedBlock = world!!.getBlockState(pos.offset(facing)) } - } - - private fun getInventory(): GroupedItemInv? { - // if we don't have an inventory, try to get one - // this happens when readAll is called before a neighbor state changes, such as immediately after world load - if (inventory?.get() == null) updateInventory() - return inventory?.get() + if (inventoryCache == null) { + inventoryCache = BlockApiCache.create(ItemStorage.SIDED, world!! as ServerWorld, pos.offset(facing)) + } + return inventoryCache!!.find(cachedFacedBlock!!, facing.opposite) } override fun handle(packet: Packet) { @@ -71,46 +69,50 @@ class InterfaceBlockEntity(pos: BlockPos, state: BlockState) : return } getInventory()?.also { inv -> - sendPacket(ReadGroupedInventoryPacket(inv, ipAddress, packet.source)) + sendPacket(ReadItemStoragePacket(inv, ipAddress, packet.source)) } } private fun handleLocateStack(packet: LocateStackPacket) { getInventory()?.also { inv -> - val amount = inv.getAmount(packet.stack) - sendPacket(StackLocationPacket(packet.stack, amount, this, ipAddress, packet.source)) + val transaction = Transaction.openOuter() + val amount = inv.simulateExtract(ItemVariant.of(packet.stack), Long.MAX_VALUE, transaction); + transaction.close() + sendPacket(StackLocationPacket(packet.stack, amount.toInt(), this, ipAddress, packet.source)) } } private fun handleExtractStack(packet: ExtractStackPacket) { getInventory()?.also { inv -> - var amount = packet.amount - while (amount > 0) { - val extracted = inv.extract(packet.stack, min(amount, packet.stack.maxCount)) - if (extracted.isEmpty) { - break - } else { - amount -= extracted.count - sendPacket(ItemStackPacket(extracted, ipAddress, packet.source)) - } + var remaining = packet.amount + while (remaining > 0) { + val toExtract = min(remaining, packet.stack.maxCount) + val transaction = Transaction.openOuter() + val extracted = inv.extract(ItemVariant.of(packet.stack), toExtract.toLong(), transaction) + transaction.commit() + remaining -= extracted.toInt() + sendPacket(ItemStackPacket(packet.stack.copyWithCount(extracted.toInt()), ipAddress, packet.source)) } } } private fun handleCheckCapacity(packet: CheckCapacityPacket) { getInventory()?.also { inv -> - val remaining = inv.attemptInsertion(packet.stack, Simulation.SIMULATE) - val couldAccept = packet.stack.count - remaining.count - sendPacket(CapacityPacket(packet.stack, couldAccept, this, ipAddress, packet.source)) + val transaction = Transaction.openOuter() + val inserted = inv.simulateInsert(ItemVariant.of(packet.stack), packet.stack.count.toLong(), transaction) + transaction.close() + sendPacket(CapacityPacket(packet.stack, inserted.toInt(), this, ipAddress, packet.source)) } } override fun doHandleItemStack(packet: ItemStackPacket): ItemStack { val inventory = getInventory() if (inventory != null) { - val remaining = inventory.insert(packet.stack) - // whatever could not be inserted will be sent back to the packet's source - return remaining + val transaction = Transaction.openOuter() + val inserted = inventory.insert(ItemVariant.of(packet.stack), packet.stack.count.toLong(), transaction) + transaction.commit() + val remaining = packet.stack.count - inserted.toInt() + return packet.stack.copyWithCount(remaining) } else { return packet.stack } diff --git a/src/main/kotlin/net/shadowfacts/phycon/block/terminal/AbstractTerminalBlockEntity.kt b/src/main/kotlin/net/shadowfacts/phycon/block/terminal/AbstractTerminalBlockEntity.kt index 3629b3e..952ee2a 100644 --- a/src/main/kotlin/net/shadowfacts/phycon/block/terminal/AbstractTerminalBlockEntity.kt +++ b/src/main/kotlin/net/shadowfacts/phycon/block/terminal/AbstractTerminalBlockEntity.kt @@ -2,6 +2,9 @@ package net.shadowfacts.phycon.block.terminal import alexiil.mc.lib.attributes.item.GroupedItemInvView import alexiil.mc.lib.attributes.item.ItemStackCollections +import net.fabricmc.fabric.api.transfer.v1.item.ItemVariant +import net.fabricmc.fabric.api.transfer.v1.storage.Storage +import net.fabricmc.fabric.api.transfer.v1.transaction.Transaction import net.minecraft.block.BlockState import net.minecraft.block.entity.BlockEntityType import net.minecraft.entity.player.PlayerEntity @@ -18,6 +21,7 @@ import net.shadowfacts.phycon.api.util.IPAddress import net.shadowfacts.phycon.block.DeviceBlockEntity import net.shadowfacts.phycon.component.* import net.shadowfacts.phycon.packet.* +import net.shadowfacts.phycon.util.Either import net.shadowfacts.phycon.util.NetworkUtil import net.shadowfacts.phycon.util.equalsIgnoringAmount import java.lang.ref.WeakReference @@ -43,13 +47,14 @@ abstract class AbstractTerminalBlockEntity(type: BlockEntityType<*>, pos: BlockP val REQUEST_INVENTORY_TIMEOUT: Long = 1 // ticks } - protected val inventoryCache = mutableMapOf() + protected val inventoryCache = mutableMapOf, GroupedItemInvView>>() val internalBuffer = TerminalBufferInventory(18) protected val pendingRequests = LinkedList() override val pendingInsertions = mutableListOf() override val dispatchStackTimeout = INSERTION_TIMEOUT + // TODO: need a ItemVariant -> Int/Long map val cachedNetItems = ItemStackCollections.intMap() private var requestInventoryTimestamp: Long? = null @@ -78,6 +83,7 @@ abstract class AbstractTerminalBlockEntity(type: BlockEntityType<*>, pos: BlockP override fun handle(packet: Packet) { when (packet) { is ReadGroupedInventoryPacket -> handleReadInventory(packet) + is ReadItemStoragePacket -> handleReadItemStorage(packet) is DeviceRemovedPacket -> handleDeviceRemoved(packet) is StackLocationPacket -> handleStackLocation(packet) is ItemStackPacket -> handleItemStack(packet) @@ -86,7 +92,12 @@ abstract class AbstractTerminalBlockEntity(type: BlockEntityType<*>, pos: BlockP } private fun handleReadInventory(packet: ReadGroupedInventoryPacket) { - inventoryCache[packet.source] = packet.inventory + inventoryCache[packet.source] = Either.Right(packet.inventory) + updateAndSync() + } + + private fun handleReadItemStorage(packet: ReadItemStoragePacket) { + inventoryCache[packet.source] = Either.Left(packet.inventory) updateAndSync() } @@ -136,9 +147,21 @@ abstract class AbstractTerminalBlockEntity(type: BlockEntityType<*>, pos: BlockP private fun updateNetItems() { cachedNetItems.clear() for (inventory in inventoryCache.values) { - for (stack in inventory.storedStacks) { - val amount = inventory.getAmount(stack) - cachedNetItems.mergeInt(stack, amount, IntBinaryOperator { a, b -> a + b }) + when (inventory) { + is Either.Left -> { + val transaction = Transaction.openOuter() + for (view in inventory.left.iterator(transaction)) { + val amount = view.amount.toInt() + cachedNetItems.mergeInt(view.resource.toStack(), amount, IntBinaryOperator { a, b -> a + b }) + } + transaction.close() + } + is Either.Right -> { + for (stack in inventory.right.storedStacks) { + val amount = inventory.right.getAmount(stack) + cachedNetItems.mergeInt(stack, amount, IntBinaryOperator { a, b -> a + b }) + } + } } } } diff --git a/src/main/kotlin/net/shadowfacts/phycon/block/terminal/TerminalBlockEntity.kt b/src/main/kotlin/net/shadowfacts/phycon/block/terminal/TerminalBlockEntity.kt index 772921d..833e00c 100644 --- a/src/main/kotlin/net/shadowfacts/phycon/block/terminal/TerminalBlockEntity.kt +++ b/src/main/kotlin/net/shadowfacts/phycon/block/terminal/TerminalBlockEntity.kt @@ -10,7 +10,6 @@ import net.minecraft.server.network.ServerPlayerEntity import net.minecraft.text.TranslatableText import net.minecraft.util.math.BlockPos import net.shadowfacts.phycon.init.PhyBlockEntities -import net.shadowfacts.phycon.packet.RequestInventoryPacket /** * @author shadowfacts diff --git a/src/main/kotlin/net/shadowfacts/phycon/component/NetworkStackDispatcher.kt b/src/main/kotlin/net/shadowfacts/phycon/component/NetworkStackDispatcher.kt index 6803e8c..33b1b69 100644 --- a/src/main/kotlin/net/shadowfacts/phycon/component/NetworkStackDispatcher.kt +++ b/src/main/kotlin/net/shadowfacts/phycon/component/NetworkStackDispatcher.kt @@ -13,6 +13,7 @@ import kotlin.math.min /** * @author shadowfacts */ +// TODO: use Transfer API transactions for this interface NetworkStackDispatcher> : ItemStackPacketHandler { diff --git a/src/main/kotlin/net/shadowfacts/phycon/util/Either.kt b/src/main/kotlin/net/shadowfacts/phycon/util/Either.kt new file mode 100644 index 0000000..7673389 --- /dev/null +++ b/src/main/kotlin/net/shadowfacts/phycon/util/Either.kt @@ -0,0 +1,10 @@ +package net.shadowfacts.phycon.util + +/** + * @author shadowfacts + */ +sealed class Either { + class Left(val left: Left) : Either() + + class Right(val right: Right) : Either() +}