diff --git a/src/main/java/ru/dbotthepony/mc/otm/OverdriveThatMatters.java b/src/main/java/ru/dbotthepony/mc/otm/OverdriveThatMatters.java index 64fb388c3..df10bdd9b 100644 --- a/src/main/java/ru/dbotthepony/mc/otm/OverdriveThatMatters.java +++ b/src/main/java/ru/dbotthepony/mc/otm/OverdriveThatMatters.java @@ -98,7 +98,7 @@ public final class OverdriveThatMatters { private void setup(final FMLCommonSetupEvent event) { MatteryNetworking.register(); - ITEM_STORAGE = StorageRegistry.register(ItemStackWrapper.class, ItemStackWrapper.EMPTY, new ImpreciseFraction("3.125")); + ITEM_STORAGE = StorageRegistry.register(ItemStackWrapper.class, ItemStackWrapper.EMPTY, new ImpreciseFraction("3.125"), false); if (ModList.get().isLoaded("mekanism")) { MinecraftForge.EVENT_BUS.register(QIOKt.class); diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/EnergyCounterBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/EnergyCounterBlockEntity.kt index edb4bf8e0..9259f7de8 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/EnergyCounterBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/EnergyCounterBlockEntity.kt @@ -198,7 +198,7 @@ class EnergyCounterBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : Mat val ioLimit = ioLimit if (ioLimit != null) { - diff = it.extractEnergy(howMuch.min(ioLimit), simulate) + diff = it.extractEnergy(howMuch.coerceAtMost(ioLimit), simulate) } else { diff = it.extractEnergy(howMuch, simulate) } @@ -230,7 +230,7 @@ class EnergyCounterBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : Mat val ioLimit = ioLimit if (ioLimit != null) { - diff = it.receiveEnergy(howMuch.min(ioLimit), simulate) + diff = it.receiveEnergy(howMuch.coerceAtMost(ioLimit), simulate) } else { diff = it.receiveEnergy(howMuch, simulate) } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MatterBottlerBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MatterBottlerBlockEntity.kt index 47331406f..19ede33ba 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MatterBottlerBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MatterBottlerBlockEntity.kt @@ -264,7 +264,7 @@ class MatterBottlerBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : if (workFlow) { if (matter.storedMatter < MATTER_EXCHANGE_RATE && graph != null) { val extracted = graph.extractMatter( - matter.missingMatter.min(MATTER_EXCHANGE_RATE * EXTRACTION_TICKS, capability.missingMatter - matter.storedMatter), true + matter.missingMatter.coerceAtMost(MATTER_EXCHANGE_RATE * EXTRACTION_TICKS).coerceAtMost(capability.missingMatter - matter.storedMatter), true ) if (extracted > ImpreciseFraction.ZERO) { @@ -277,7 +277,7 @@ class MatterBottlerBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : val energyExtracted = energy.extractEnergyInner(ENERGY_CONSUMPTION, true) if (!energyExtracted.isZero) { - val matter = capability.receiveMatterOuter(MATTER_EXCHANGE_RATE.min(matter.storedMatter) * energyExtracted / ENERGY_CONSUMPTION, true) + val matter = capability.receiveMatterOuter(MATTER_EXCHANGE_RATE.coerceAtMost(matter.storedMatter) * energyExtracted / ENERGY_CONSUMPTION, true) if (!matter.isZero) { energy.extractEnergyInner(ENERGY_CONSUMPTION * matter / MATTER_EXCHANGE_RATE, false) @@ -301,7 +301,7 @@ class MatterBottlerBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : val energyExtracted = energy.extractEnergyInner(ENERGY_CONSUMPTION, true) if (!energyExtracted.isZero) { - val matter = capability.extractMatterOuter(MATTER_EXCHANGE_RATE.min(matter.missingMatter) * energyExtracted / ENERGY_CONSUMPTION, true) + val matter = capability.extractMatterOuter(MATTER_EXCHANGE_RATE.coerceAtMost(matter.missingMatter) * energyExtracted / ENERGY_CONSUMPTION, true) if (!matter.isZero) { this.energy.extractEnergyInner(ENERGY_CONSUMPTION * matter / MATTER_EXCHANGE_RATE,false) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MatterReplicatorBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MatterReplicatorBlockEntity.kt index d4f655db3..9e3bc61f5 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MatterReplicatorBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MatterReplicatorBlockEntity.kt @@ -159,7 +159,7 @@ class MatterReplicatorBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : // в тик требуется меньше материи, чем её может хранить репликатор // примем из сети недостающее количество бака материи, или 200 тиков репликации, что меньше val toExtract = - matter.missingMatter.min(drainPerTick.times(DRAIN_MULT)) + matter.missingMatter.coerceAtMost(drainPerTick.times(DRAIN_MULT)) val drain = graph.extractMatter(toExtract, true) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/StorageBusBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/StorageBusBlockEntity.kt index 375b848dc..aca8f4e01 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/StorageBusBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/StorageBusBlockEntity.kt @@ -37,8 +37,20 @@ import java.util.* import kotlin.collections.ArrayList private class SlotTuple(val slot: Int, val stack: ItemStack) -private class TrackedTuple(val stack: ItemStackWrapper, val id: UUID) { +private class TrackedTuple(override val stack: ItemStackWrapper, override val id: UUID) : IStorageTuple { val children = Int2ObjectAVLTreeMap() + + override fun toString(): String { + return "TrackedTuple[$stack $id]" + } +} + +private fun Long.clamp(): Int { + if (this > Int.MAX_VALUE) { + return Int.MAX_VALUE + } + + return this.toInt() } private class ItemHandlerComponent(private val parent: IItemHandler) : IStorageComponent { @@ -77,14 +89,24 @@ private class ItemHandlerComponent(private val parent: IItemHandler) : IStorageC if (scannedMap.stack.count.isPositive) { for (listener in listeners) { - listener.changeStack(scannedMap.stack, scannedMap.id, count) + listener.changeStack(scannedMap, count) } } else { for (listener in listeners) { - listener.removeStack(scannedMap.stack, scannedMap.id) + listener.removeStack(scannedMap) } index.remove(scannedMap.id) + + val listing = partitioned[scannedMap.stack.longHashCode] ?: throw IllegalStateException("Item listing is not present for ${scannedMap.stack}") + + if (listing.remove(scannedMap)) { + if (listing.isEmpty()) { + partitioned.remove(scannedMap.stack.longHashCode) + } + } else { + throw IllegalStateException("Item listing for ${scannedMap.stack} did not contain $scannedMap") + } } } @@ -104,11 +126,11 @@ private class ItemHandlerComponent(private val parent: IItemHandler) : IStorageC private fun addTracked(slot: Int, stack: ItemStack) { check(scannedMap[slot] == null) { "Already tracking slot $slot" } - val listing = partitioned.computeIfAbsent(ItemStackWrapper.partitionKey(stack.item), Long2ObjectFunction { ArrayList() }) + val listing = partitioned.computeIfAbsent(ItemStackWrapper.longHashCode(stack.item), Long2ObjectFunction { ArrayList() }) var tuple: TrackedTuple? = null for (storedTuple in listing) { - if (ItemStack.isSameItemSameTags(stack, storedTuple.stack.stack)) { + if (storedTuple.stack.sameItem(stack)) { tuple = storedTuple break } @@ -118,7 +140,7 @@ private class ItemHandlerComponent(private val parent: IItemHandler) : IStorageC var oldCount = ImpreciseFraction.ZERO if (added) { - val storageStack = ItemStackWrapper(stack.copy()) + val storageStack = ItemStackWrapper(stack) tuple = TrackedTuple(storageStack, UUID.randomUUID()) index[tuple.id] = tuple listing.add(tuple) @@ -232,9 +254,9 @@ private class ItemHandlerComponent(private val parent: IItemHandler) : IStorageC if (!amount.isPositive) return ItemStackWrapper.EMPTY - val intAmount = amount.toInt() + val intAmount = amount.toLong() val tuple = index[id] ?: return ItemStackWrapper.EMPTY - var totalExtracted = 0 + var totalExtracted = 0L val iter = tuple.children.values.iterator() val listCopy = Array(tuple.children.values.size) { @@ -244,14 +266,14 @@ private class ItemHandlerComponent(private val parent: IItemHandler) : IStorageC val copy = tuple.stack.copy() for (stack in listCopy) { - val extracted = parent.extractItem(stack.slot, stack.stack.count.coerceAtMost(intAmount - totalExtracted), true) + val extracted = parent.extractItem(stack.slot, stack.stack.count.coerceAtMost((intAmount - totalExtracted).clamp()), true) if (extracted.isEmpty) { // dummy condition continue - } else if (ItemStack.isSameItemSameTags(extracted, tuple.stack.stack)) { + } else if (tuple.stack.sameItem(extracted)) { if (!simulate) { - parent.extractItem(stack.slot, stack.stack.count.coerceAtMost(intAmount - totalExtracted), false) + parent.extractItem(stack.slot, stack.stack.count.coerceAtMost((intAmount - totalExtracted).clamp()), false) } totalExtracted += extracted.count @@ -269,11 +291,11 @@ private class ItemHandlerComponent(private val parent: IItemHandler) : IStorageC } } - if (totalExtracted == 0) { + if (totalExtracted == 0L) { return ItemStackWrapper.EMPTY } - copy.setCount(totalExtracted) + copy.count = ImpreciseFraction(totalExtracted) return copy } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/EnergyStorageImpl.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/EnergyStorageImpl.kt index 2cb1c0bdf..e11221a46 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/EnergyStorageImpl.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/EnergyStorageImpl.kt @@ -74,7 +74,7 @@ sealed class ItemEnergyStorageImpl( val maxOutput = maxOutput if (maxOutput != null) { - howMuch = howMuch.min(maxOutput) + howMuch = howMuch.coerceAtMost(maxOutput) } val batteryLevel = batteryLevel @@ -101,7 +101,7 @@ sealed class ItemEnergyStorageImpl( val maxInput = maxInput if (maxInput != null) { - howMuch = howMuch.min(maxInput) + howMuch = howMuch.coerceAtMost(maxInput) } val batteryLevel = batteryLevel @@ -109,7 +109,7 @@ sealed class ItemEnergyStorageImpl( if (batteryLevel >= maxBatteryLevel) return ImpreciseFraction.ZERO - val newLevel = (batteryLevel + howMuch).min(maxBatteryLevel) + val newLevel = (batteryLevel + howMuch).coerceAtMost(maxBatteryLevel) val diff = (newLevel - batteryLevel) if (!simulate && batteryLevel != newLevel) { @@ -173,7 +173,7 @@ sealed class BlockEnergyStorageImpl constructor( val maxOutput = maxOutput if (maxOutput != null) { - howMuch = howMuch.min(maxOutput) + howMuch = howMuch.coerceAtMost(maxOutput) } if (!batteryLevel.isPositive) @@ -199,13 +199,13 @@ sealed class BlockEnergyStorageImpl constructor( val maxInput = maxInput if (maxInput != null) { - howMuch = howMuch.min(maxInput) + howMuch = howMuch.coerceAtMost(maxInput) } if (batteryLevel >= maxBatteryLevel) return ImpreciseFraction.ZERO - val newLevel = (batteryLevel + howMuch).min(maxBatteryLevel) + val newLevel = (batteryLevel + howMuch).coerceAtMost(maxBatteryLevel) val diff = (newLevel - batteryLevel) if (!simulate && batteryLevel != newLevel) { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/android/AndroidCapability.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/android/AndroidCapability.kt index 8b71a22fb..752fc8051 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/android/AndroidCapability.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/android/AndroidCapability.kt @@ -382,7 +382,7 @@ open class AndroidCapability(final override val entity: LivingEntity) : ICapabil } } - val new = (battery + howMuch).min(maxBattery) + val new = (battery + howMuch).coerceAtMost(maxBattery) received += new - battery if (!simulate) { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/drive/AbstractMatteryDrive.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/drive/AbstractMatteryDrive.kt index c08248f23..4f74f99b0 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/drive/AbstractMatteryDrive.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/drive/AbstractMatteryDrive.kt @@ -40,10 +40,10 @@ abstract class AbstractMatteryDrive @JvmOverloads constructor @Suppress("unchecked_cast") override fun insertStack(stack: T, simulate: Boolean): T { - val maxInsert = driveCapacity.minus(storedCount).min(stack.count) + val maxInsert = driveCapacity.minus(storedCount).coerceAtMost(stack.count) if (maxInsert <= ImpreciseFraction.ZERO) return stack - val listing = storedStacks.computeIfAbsent(stack.partitionKey, Long2ObjectFunction { ArrayList() }) + val listing = storedStacks.computeIfAbsent(stack.longHashCode, Long2ObjectFunction { ArrayList() }) for (state in listing) { if (state.stack.sameItem(stack)) { @@ -98,10 +98,13 @@ abstract class AbstractMatteryDrive @JvmOverloads constructor var amount = amount val get = storedByID[id] ?: return storageType.empty - if (amount <= ImpreciseFraction.ZERO) + if (!storageType.fractional) + amount = amount.floor() + + if (!amount.isPositive) amount = get.stack.maxStackSize ?: get.stack.count - amount = amount.min(get.stack.count) + amount = amount.coerceAtMost(get.stack.count) if (amount <= ImpreciseFraction.ZERO) return storageType.empty @@ -110,8 +113,8 @@ abstract class AbstractMatteryDrive @JvmOverloads constructor copy.count = amount if (!simulate) { - if (amount.compareTo(get.stack.count) == 0) { - val listing = storedStacks[get.stack.partitionKey]!! + if (amount == get.stack.count) { + val listing = storedStacks[get.stack.longHashCode] ?: throw IllegalStateException("Can't find storage list with partition ${get.stack.longHashCode} for ${get.stack}") listing.remove(get) storedDifferentStacks-- @@ -120,7 +123,7 @@ abstract class AbstractMatteryDrive @JvmOverloads constructor } if (listing.size == 0) { - storedStacks.remove(get.stack.partitionKey) + storedStacks.remove(get.stack.longHashCode) } } @@ -128,7 +131,7 @@ abstract class AbstractMatteryDrive @JvmOverloads constructor val oldCount = get.stack.count get.stack.shrink(amount) - if (!get.stack.count.isZero) { + if (get.stack.count.isPositive) { for (listener in listeners) { listener.changeStack(get.stack, get.id, oldCount) } @@ -182,13 +185,13 @@ abstract class AbstractMatteryDrive @JvmOverloads constructor val stack = deserializeStack(entry) if (stack != null) { - storedCount = storedCount.plus(stack.count) + storedCount += stack.count storedDifferentStacks++ val id = entry.getLongArray("id") val tuple = StorageTuple(if (id.size == 2) UUID(id[0], id[1]) else UUID.randomUUID(), stack) - storedStacks.computeIfAbsent(stack.partitionKey, Long2ObjectFunction { ArrayList() }).add(tuple) + storedStacks.computeIfAbsent(stack.longHashCode, Long2ObjectFunction { ArrayList() }).add(tuple) storedByID[tuple.id] = tuple } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/drive/ItemMatteryDrive.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/drive/ItemMatteryDrive.kt index ad3d8e887..919e64ffe 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/drive/ItemMatteryDrive.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/drive/ItemMatteryDrive.kt @@ -28,12 +28,12 @@ class ItemMatteryDrive : AbstractMatteryDrive, IItemMatteryDri override fun serializeStack(item: IStorageTuple): CompoundTag? { val tag = CompoundTag() - val location = item.stack.stack.item.registryName!!.toString() + val location = item.stack.registryName.toString() tag["item"] = location - tag["count"] = item.stack.stack.count + tag["count"] = item.stack.count.serializeNBT() - val itag = item.stack.stack.tag + val itag = item.stack.item.tag if (itag != null) { tag["data"] = itag @@ -56,32 +56,22 @@ class ItemMatteryDrive : AbstractMatteryDrive, IItemMatteryDri } override fun findItems(item: Item): List> { - val list = storedStacks[ItemStackWrapper.partitionKey(item)] + val list = storedStacks[ItemStackWrapper.longHashCode(item)] return if (list != null) java.util.List.copyOf(list) else emptyList() } override fun findItems(stack: ItemStack): List> { - val list = storedStacks[ItemStackWrapper.partitionKey(stack.item)] ?: return emptyList() + val list = storedStacks[ItemStackWrapper.longHashCode(stack.item)] ?: return emptyList() - var amount = 0 + val buildList = ArrayList>() for (_stack in list) { - if (ItemStack.tagMatches(_stack.stack.stack, stack)) { - amount++ + if (_stack.stack.sameItem(stack)) { + buildList.add(_stack) } } - val build_list = ArrayList>(amount) - var i = 0 - - for (_stack in list) { - if (ItemStack.tagMatches(_stack.stack.stack, stack)) { - build_list[i] = _stack - i++ - } - } - - return build_list + return buildList } companion object { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/matter/MatterHandlerImpl.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/matter/MatterHandlerImpl.kt index 915c122a2..45a24fc9a 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/matter/MatterHandlerImpl.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/matter/MatterHandlerImpl.kt @@ -45,9 +45,9 @@ open class MatterHandlerImpl @JvmOverloads constructor( val new: ImpreciseFraction if (maxReceive == null) { - new = (storedMatter + howMuch).min(maxStoredMatter) + new = (storedMatter + howMuch).coerceAtMost(maxStoredMatter) } else { - new = (storedMatter + howMuch.min(maxReceive)).min(maxStoredMatter) + new = (storedMatter + howMuch.coerceAtMost(maxReceive)).coerceAtMost(maxStoredMatter) } val diff = new - storedMatter @@ -73,7 +73,7 @@ open class MatterHandlerImpl @JvmOverloads constructor( if (maxExtract == null) { new = (storedMatter - howMuch).moreThanZero() } else { - new = (storedMatter - howMuch.min(maxExtract)).moreThanZero() + new = (storedMatter - howMuch.coerceAtMost(maxExtract)).moreThanZero() } val diff = storedMatter - new diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/compat/mekanism/QIO.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/mekanism/QIO.kt index 6e4f27a15..0aa17b7bf 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/compat/mekanism/QIO.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/mekanism/QIO.kt @@ -6,7 +6,6 @@ import it.unimi.dsi.fastutil.objects.Object2ObjectFunction import mekanism.api.math.MathUtils import mekanism.common.content.qio.QIOFrequency import mekanism.common.content.qio.QIOFrequency.QIOItemTypeData -import mekanism.common.content.transporter.TransporterManager import mekanism.common.lib.frequency.Frequency import mekanism.common.lib.inventory.HashedItem import mekanism.common.tile.qio.TileEntityQIODriveArray @@ -24,6 +23,7 @@ import ru.dbotthepony.mc.otm.addPostServerTickerOnce import ru.dbotthepony.mc.otm.capability.MatteryCapability import ru.dbotthepony.mc.otm.capability.isMekanismLoaded import ru.dbotthepony.mc.otm.core.ImpreciseFraction +import ru.dbotthepony.mc.otm.core.toImpreciseFraction import ru.dbotthepony.mc.otm.graph.storage.BasicStorageGraphNode import ru.dbotthepony.mc.otm.graph.storage.StorageNetworkGraph import ru.dbotthepony.mc.otm.storage.* @@ -58,13 +58,13 @@ private class QIOFrequencyAccess(val parent: QIOFrequency) : IStorageComponent copy.count) { // expecting stack to be still present in QIO storage grid @@ -147,16 +147,16 @@ private class QIOFrequencyAccess(val parent: QIOFrequency) : IStorageComponent this + other.toFloat() + is Double -> this + other.toDouble() + is Int -> this + other.toInt() + is Long -> this + other.toLong() + is Short -> this + other.toShort() + is Byte -> this + other.toByte() + + is ImpreciseFraction -> { + when (other) { + is Float -> this + other.toFloat() + is Double -> this + other.toDouble() + is Int, is Byte, is Short -> this + other.toInt() + is Long -> this + other.toLong() + is ImpreciseFraction -> this + other + is Fraction -> this + other.toImpreciseFraction() + else -> throw IllegalStateException("I don't know what to do with ${other::class.qualifiedName} (right hand operand)") + } + } + + is Fraction -> { + when (other) { + is Float -> this + other.toFloat() + is Double -> this + other.toDouble() + is Int, is Byte, is Short -> this + other.toInt() + is Long -> this + other.toLong() + is ImpreciseFraction -> this + other.toFraction() + is Fraction -> this + other + else -> throw IllegalStateException("I don't know what to do with ${other::class.qualifiedName} (right hand operand)") + } + } + + else -> throw IllegalStateException("I don't know what to do with ${this::class.qualifiedName} (left hand operand)") + } +} + +/** + * Performs type check+cast and subtracts two numbers. + * + * Returns the same type as on input (adding Double to Float will not return Double, etc.) + */ +fun Number.dynMinus(other: Number): Number { + return when (this) { + is Float -> this - other.toFloat() + is Double -> this - other.toDouble() + is Int -> this - other.toInt() + is Long -> this - other.toLong() + is Short -> this - other.toShort() + is Byte -> this - other.toByte() + + is ImpreciseFraction -> { + when (other) { + is Float -> this - other.toFloat() + is Double -> this - other.toDouble() + is Int, is Byte, is Short -> this - other.toInt() + is Long -> this - other.toLong() + is ImpreciseFraction -> this - other + is Fraction -> this - other.toImpreciseFraction() + else -> throw IllegalStateException("I don't know what to do with ${other::class.qualifiedName} (right hand operand)") + } + } + + is Fraction -> { + when (other) { + is Float -> this - other.toFloat() + is Double -> this - other.toDouble() + is Int, is Byte, is Short -> this - other.toInt() + is Long -> this - other.toLong() + is ImpreciseFraction -> this - other.toFraction() + is Fraction -> this - other + else -> throw IllegalStateException("I don't know what to do with ${other::class.qualifiedName} (right hand operand)") + } + } + + else -> throw IllegalStateException("I don't know what to do with ${this::class.qualifiedName} (left hand operand)") + } +} + +/** + * Performs type check+cast and multiplies two numbers. + * + * Returns the same type as on input (adding Double to Float will not return Double, etc.) + */ +fun Number.dynTimes(other: Number): Number { + return when (this) { + is Float -> this * other.toFloat() + is Double -> this * other.toDouble() + is Int -> this * other.toInt() + is Long -> this * other.toLong() + is Short -> this * other.toShort() + is Byte -> this * other.toByte() + + is ImpreciseFraction -> { + when (other) { + is Float -> this * other.toFloat() + is Double -> this * other.toDouble() + is Int, is Byte, is Short -> this * other.toInt() + is Long -> this * other.toLong() + is ImpreciseFraction -> this * other + is Fraction -> this * other.toImpreciseFraction() + else -> throw IllegalStateException("I don't know what to do with ${other::class.qualifiedName} (right hand operand)") + } + } + + is Fraction -> { + when (other) { + is Float -> this * other.toFloat() + is Double -> this * other.toDouble() + is Int, is Byte, is Short -> this * other.toInt() + is Long -> this * other.toLong() + is ImpreciseFraction -> this * other.toFraction() + is Fraction -> this * other + else -> throw IllegalStateException("I don't know what to do with ${other::class.qualifiedName} (right hand operand)") + } + } + + else -> throw IllegalStateException("I don't know what to do with ${this::class.qualifiedName} (left hand operand)") + } +} + +/** + * Performs type check+cast and divides two numbers. + * + * Returns the same type as on input (adding Double to Float will not return Double, etc.) + */ +fun Number.dynDiv(other: Number): Number { + return when (this) { + is Float -> this / other.toFloat() + is Double -> this / other.toDouble() + is Int -> this / other.toInt() + is Long -> this / other.toLong() + is Short -> this / other.toShort() + is Byte -> this / other.toByte() + + is ImpreciseFraction -> { + when (other) { + is Float -> this / other.toFloat() + is Double -> this / other.toDouble() + is Int, is Byte, is Short -> this / other.toInt() + is Long -> this / other.toLong() + is ImpreciseFraction -> this / other + is Fraction -> this / other.toImpreciseFraction() + else -> throw IllegalStateException("I don't know what to do with ${other::class.qualifiedName} (right hand operand)") + } + } + + is Fraction -> { + when (other) { + is Float -> this / other.toFloat() + is Double -> this / other.toDouble() + is Int, is Byte, is Short -> this / other.toInt() + is Long -> this / other.toLong() + is ImpreciseFraction -> this / other.toFraction() + is Fraction -> this / other + else -> throw IllegalStateException("I don't know what to do with ${other::class.qualifiedName} (right hand operand)") + } + } + + else -> throw IllegalStateException("I don't know what to do with ${this::class.qualifiedName} (left hand operand)") + } +} + +val Number.isFractional get() = this is Float || this is Double || this is ImpreciseFraction || this is Fraction +val Number.isWhole get() = !isFractional + +fun Number.toImpreciseFraction(): ImpreciseFraction { + return when (this) { + is ImpreciseFraction -> this + is Float -> ImpreciseFraction(this) + is Double -> ImpreciseFraction(this) + is Int -> ImpreciseFraction(this) + is Byte -> ImpreciseFraction(this) + is Short -> ImpreciseFraction(this) + is Long -> ImpreciseFraction(this) + is Fraction -> this.toImpreciseFraction() + else -> throw ClassCastException("Can not turn $this into ImpreciseFraction") + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/Fraction.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/Fraction.kt index a93b1ae6a..2b7c8d6d8 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/Fraction.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/Fraction.kt @@ -10,8 +10,6 @@ import java.math.BigInteger import java.math.MathContext import java.math.RoundingMode -private val IMPRECISE_CONTEXT = MathContext(17) - private fun powScale(int: Int): BigInteger { if (int <= 0) return BigInteger.ONE @@ -118,6 +116,7 @@ private val GCDs = arrayOf( private val COMPACT_THRESHOLD = BigInteger("1000000000000000000000") +@Suppress("NAME_SHADOWING") private fun compactTwo(value1: BigInteger, value2: BigInteger, compact: Boolean = true): Fraction { when (value1.signum()) { 0 -> return Fraction(value1, value2, compact = compact) @@ -218,6 +217,7 @@ private fun unsignedInt(value: Byte): Int { private data class MagnitudeCrunchResult(val value: BigInteger, val mag: Int) +@Suppress("NAME_SHADOWING") private fun magnitude(value: BigInteger): MagnitudeCrunchResult { if (isZero(value)) return MagnitudeCrunchResult(value, 0) @@ -238,9 +238,12 @@ private fun magnitude(value: BigInteger): MagnitudeCrunchResult { return MagnitudeCrunchResult(value, mag) } -@JvmRecord @Suppress("unused") -data class Fraction @JvmOverloads constructor(@JvmField val value: BigInteger, @JvmField val divisor: BigInteger = BigInteger.ONE, @JvmField val compact: Boolean = true) : Comparable { +class Fraction @JvmOverloads constructor( + val value: BigInteger, + val divisor: BigInteger = BigInteger.ONE, + val compact: Boolean = true +) : Number(), Comparable { @JvmOverloads constructor(value: Long, compact: Boolean = true) : this(BigInteger.valueOf(value), compact = compact) @JvmOverloads constructor(value: Int, compact: Boolean = true) : this(BigInteger.valueOf(value.toLong()), compact = compact) @JvmOverloads constructor(value: Float, compact: Boolean = true) : this(BigDecimal(value.toString()), compact = compact) @@ -589,12 +592,12 @@ data class Fraction @JvmOverloads constructor(@JvmField val value: BigInteger, @ } // Преобразования - fun toFloat(): Float { + override fun toFloat(): Float { if (isNaN()) return Float.NaN return (value / divisor).toFloat() + ((value % divisor).toFloat() / divisor.toFloat()) } - fun toDouble(): Double { + override fun toDouble(): Double { if (isNaN()) return Double.NaN return (value / divisor).toDouble() + ((value % divisor).toDouble() / divisor.toDouble()) } @@ -607,7 +610,7 @@ data class Fraction @JvmOverloads constructor(@JvmField val value: BigInteger, @ val a = divisor.toDouble() val b = div[1].toDouble() - if (b == 0.0 || b == -0.0 || !a.isFinite() || !b.isFinite()) { + if (b == 0.0 || !a.isFinite() || !b.isFinite()) { return ImpreciseFraction(div[0]) } @@ -650,16 +653,28 @@ data class Fraction @JvmOverloads constructor(@JvmField val value: BigInteger, @ ) } - fun toInt(): Int { + override fun toInt(): Int { if (isNaN()) throw ArithmeticException("Fraction is not a number") return (value / divisor).toInt() } - fun toLong(): Long { + override fun toLong(): Long { if (isNaN()) throw ArithmeticException("Fraction is not a number") return (value / divisor).toLong() } + override fun toByte(): Byte { + return toInt().toByte() + } + + override fun toChar(): Char { + return toInt().toChar() + } + + override fun toShort(): Short { + return toInt().toShort() + } + @JvmOverloads fun toBigDecimal(context: MathContext = DEFAULT_MATH_CONTEXT): BigDecimal { if (isNaN()) throw ArithmeticException("Fraction is not a number") diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/ImpreciseFraction.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/ImpreciseFraction.kt index 7c075716d..7f764dd06 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/ImpreciseFraction.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/ImpreciseFraction.kt @@ -68,6 +68,11 @@ private fun bytesToLongBE( ) } +/** + * Constant value which represent edge of meaningful bits. + * + * Equals to 0.000000000001 + */ const val EPSILON = 0.000000000001 fun weakEqualDoubles(a: Double, b: Double): Boolean { @@ -110,8 +115,20 @@ private const val MEANINGFUL_BITS_DOUBLE = MEANINGFUL_BITS_LONG.toDouble() private val MEANINGFUL_BITS_CONTEXT = MathContext(MEANINGFUL_BITS) private val MEANINGFUL_BITS_BI = BigInteger.valueOf(MEANINGFUL_BITS_LONG) +/** + * Imprecise Fraction class for dealing with huge numbers that need fractional part to some extent. + * + * In essence, this class is pretty much like [BigDecimal], except decimal part of this number is + * not guaranteed to be precise (stored as [Double]). The reason behind creation of this class, however, + * is to allow infinite precision of [whole] part, while leaving [decimal] part to be rounded in inexact operations. + * + * This class is value based, however, [equals] and [compareTo] are not doing *exact* comparison. Whole part is always compared + * exactly, but decimal part is considered equal if their difference is less than [EPSILON]. + * + * + */ @Suppress("unused") -class ImpreciseFraction @JvmOverloads constructor(whole: BigInteger, decimal: Double = 0.0) : Comparable { +class ImpreciseFraction @JvmOverloads constructor(whole: BigInteger, decimal: Double = 0.0) : Number(), Comparable { @JvmOverloads constructor(whole: Byte, decimal: Double = 0.0) : this(BigInteger.valueOf(whole.toLong()), decimal) @JvmOverloads constructor(whole: Short, decimal: Double = 0.0) : this(BigInteger.valueOf(whole.toLong()), decimal) @JvmOverloads constructor(whole: Int, decimal: Double = 0.0) : this(BigInteger.valueOf(whole.toLong()), decimal) @@ -121,7 +138,14 @@ class ImpreciseFraction @JvmOverloads constructor(whole: BigInteger, decimal: Do constructor(value: Double) : this(BigInteger.valueOf((value - value % 1.0).toLong()), value % 1.0) constructor(value: String) : this(BigDecimal(value)) + /** + * Inexact fractional part of this fraction + */ val decimal: Double + + /** + * Exact whole part of this fraction + */ val whole: BigInteger init { @@ -136,8 +160,8 @@ class ImpreciseFraction @JvmOverloads constructor(whole: BigInteger, decimal: Do decimal = frac } - // дробная часть равна нулю - if (decimal == 0.0) { + // дробная часть равна или очень близка к нулю + if (weakEqualDoubles(decimal, 0.0)) { if (!isZero(whole)) { this.decimal = 0.0 this.whole = whole @@ -379,6 +403,11 @@ class ImpreciseFraction @JvmOverloads constructor(whole: BigInteger, decimal: Do return sign } + // exactly zero + if (decimal == 0.0) + return if (1.0 / decimal > 0) 1 else -1 + + // inexactly zero if (weakEqualDoubles(decimal, 0.0)) return 0 @@ -458,31 +487,7 @@ class ImpreciseFraction @JvmOverloads constructor(whole: BigInteger, decimal: Do return ZERO } - fun max(vararg others: ImpreciseFraction): ImpreciseFraction { - var max = this - - for (other in others) { - if (max < other) { - max = other - } - } - - return max - } - - fun min(vararg others: ImpreciseFraction): ImpreciseFraction { - var min = this - - for (other in others) { - if (min > other) { - min = other - } - } - - return min - } - - fun toInt(): Int { + override fun toInt(): Int { if (whole > BI_INT_MAX) { return Int.MAX_VALUE } else if (whole < BI_INT_MIN) { @@ -499,14 +504,36 @@ class ImpreciseFraction @JvmOverloads constructor(whole: BigInteger, decimal: Do buff.writeBytes(longToBytesBE(decimal.toBits())) } - fun toFloat(): Float { + override fun toFloat(): Float { return whole.toFloat() + decimal.toFloat() } - fun toDouble(): Double { + override fun toDouble(): Double { return whole.toDouble() + decimal } + override fun toByte(): Byte { + return toInt().toByte() + } + + override fun toChar(): Char { + return toInt().toChar() + } + + override fun toLong(): Long { + if (whole > BI_LONG_MAX) { + return Long.MAX_VALUE + } else if (whole < BI_LONG_MIN) { + return Long.MIN_VALUE + } + + return whole.toLong() + } + + override fun toShort(): Short { + return toInt().toShort() + } + fun toFraction(): Fraction { if (isZero) { return Fraction.ZERO diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/item/MatterCapacitorItem.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/item/MatterCapacitorItem.kt index 8d97406c6..22a02c227 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/item/MatterCapacitorItem.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/item/MatterCapacitorItem.kt @@ -61,7 +61,7 @@ class MatterCapacitorItem : Item { override fun receiveMatterInner(howMuch: ImpreciseFraction, simulate: Boolean): ImpreciseFraction { if (isCreative) return howMuch - val new = matter().plus(howMuch.min(maxInput)).min(storage) + val new = matter().plus(howMuch.coerceAtMost(maxInput)).coerceAtMost(storage) val diff = new.minus(matter()) if (!simulate) { @@ -77,7 +77,7 @@ class MatterCapacitorItem : Item { override fun extractMatterInner(howMuch: ImpreciseFraction, simulate: Boolean): ImpreciseFraction { if (isCreative) return howMuch - val new = matter().minus(howMuch.min(maxOutput)).moreThanZero() + val new = matter().minus(howMuch.coerceAtMost(maxOutput)).moreThanZero() val diff = matter().minus(new) if (!simulate) { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/item/MatterDustItem.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/item/MatterDustItem.kt index b0038ce7e..24f92dd35 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/item/MatterDustItem.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/item/MatterDustItem.kt @@ -58,7 +58,7 @@ class MatterDustItem : Item(Properties().tab(OverdriveThatMatters.INSTANCE.CREAT if (matterThis >= MAX_MATTER_IN_ITEM) return ImpreciseFraction.ZERO - val newMatter = (matterThis + matter).min(MAX_MATTER_IN_ITEM) + val newMatter = (matterThis + matter).coerceAtMost(MAX_MATTER_IN_ITEM) val diff = newMatter - matterThis if (!simulate) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/item/weapon/PlasmaWeaponItem.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/item/weapon/PlasmaWeaponItem.kt index 2ff05a0a9..cacd57500 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/item/weapon/PlasmaWeaponItem.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/item/weapon/PlasmaWeaponItem.kt @@ -112,7 +112,7 @@ class PlasmaWeaponEnergy(val itemStack: ItemStack, private val innerCapacity: Im return totalReceived } - val newEnergy = (innerBatteryLevel - howMuch).min(innerCapacity) + val newEnergy = (innerBatteryLevel - howMuch).coerceAtMost(innerCapacity) val diff = newEnergy - innerBatteryLevel if (!simulate) { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/data/NetworkedItemView.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/data/NetworkedItemView.kt index a63df295d..c478e7be3 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/data/NetworkedItemView.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/data/NetworkedItemView.kt @@ -165,8 +165,8 @@ open class NetworkedItemView(val ply: Player, val menu: MatteryMenu, val remote: if (!extracted.isEmpty) { val (_, remaining) = menu.quickMoveToInventory(extracted.stack, false) - if (remaining.count != extracted.stack.count) { - provider.extractStack(state.upstreamId, extracted.stack.count - remaining.count, false) + if (remaining.count != extracted.count.toInt()) { + provider.extractStack(state.upstreamId, extracted.count.toInt() - remaining.count, false) } } @@ -180,7 +180,7 @@ open class NetworkedItemView(val ply: Player, val menu: MatteryMenu, val remote: val amount = carried.count if (amount == carried.count) { - val (stack) = provider.insertStack(ItemStackWrapper(menu.carried), false) + val stack = provider.insertStack(ItemStackWrapper(menu.carried), false).stack menu.carried = stack MatteryNetworking.send(ply as ServerPlayer, SetCarriedPacket(menu.carried)) menu.setRemoteCarried(menu.carried.copy()) @@ -202,7 +202,7 @@ open class NetworkedItemView(val ply: Player, val menu: MatteryMenu, val remote: if (action == ClickAction.PRIMARY) state.stack.maxStackSize else - Math.max(1, state.stack.maxStackSize / 2) + 1.coerceAtLeast(state.stack.maxStackSize / 2) val extracted = provider.extractStack(state.upstreamId!!, amount, false) menu.carried = extracted.stack diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/storage/API.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/storage/API.kt index 3fba57431..e73b9ec8e 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/storage/API.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/storage/API.kt @@ -7,6 +7,18 @@ import net.minecraftforge.registries.ForgeRegistry interface IStorageStack { fun copy(): IStorageStack + /** + * Despite count being [ImpreciseFraction], far not all items can be "fraction" + * into pieces (e.g. ItemStacks). + * + * Consult [StorageStackType.fractional] to see if item has actual fractions. + * + * Implementation MUST [ImpreciseFraction.floor] received value if fractions + * are not supported, and NOT throw any errors. Writing fractional value is a correct + * behavior, and is used in base mod. + * + * Behavior of writing negative value is undefined. + */ var count: ImpreciseFraction val isEmpty: Boolean @@ -17,15 +29,17 @@ interface IStorageStack { val maxStackSize: ImpreciseFraction? get() = null /** + * In base mod, a [hashCode] but for use with AVL trees. + * * This property represent (almost) unique key used to quickly look up for similar stacks. - * Semantic is very similar, but not equal, to [hashCode] of object. This property **must** be equal for two - * entries if [sameItem] returns true. This value should not change. + * This property **must** be equal for two entries if [sameItem] returns true. + * This value should not change. * * A good example of key is [ForgeRegistry.getID]. * - * Collisions are solved as in open hash map, with linear scan for required item. + * Users of this property should prefer binary search trees (e.g. AVL) with this value as key. */ - val partitionKey: Long + val longHashCode: Long /** * [other] is the stack to compare. diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/storage/ItemStackWrapper.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/storage/ItemStackWrapper.kt index 7a3d817f6..92c2ddd83 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/storage/ItemStackWrapper.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/storage/ItemStackWrapper.kt @@ -6,40 +6,71 @@ import net.minecraftforge.registries.ForgeRegistries import net.minecraftforge.registries.ForgeRegistry import ru.dbotthepony.mc.otm.core.ImpreciseFraction -class ItemStackWrapper(val stack: ItemStack) : IStorageStack { - operator fun component1() = stack +/** + * constructors always copy its input. + */ +class ItemStackWrapper : IStorageStack { + /** + * [ItemStack] representing what item is this. + * + * In most cases you want to use [stack] instead. + */ + val item: ItemStack + val registryName get() = item.item.registryName!! + override var count: ImpreciseFraction + set(value) { field = value.floor() } + + constructor(item: ItemStack) { + this.item = item.copy() + this.count = ImpreciseFraction(item.count) + this.item.count = 1 + this.longHashCode = longHashCode(this.item.item) + } + + constructor(item: ItemStackWrapper) { + this.item = item.item.copy() + this.count = item.count + this.item.count = 1 + // this.longHashCode = longHashCode(this.item.item) + this.longHashCode = item.longHashCode + } override fun copy(): ItemStackWrapper { - return ItemStackWrapper(stack.copy()) + return ItemStackWrapper(this) } - fun setCount(value: Int) { - stack.count = value.coerceAtLeast(0) + override val maxStackSize get() = ImpreciseFraction(item.maxStackSize) + + override val isEmpty: Boolean get() = item.isEmpty || !count.isPositive + override val longHashCode: Long + + /** + * [ItemStack] with valid amount and which can be modified after + */ + val stack: ItemStack get() = item.copy().also { it.count = count.toInt() } + + override fun toString(): String { + return "ItemStackWrapper[$count $registryName]" } - override var count: ImpreciseFraction - get() = ImpreciseFraction(stack.count) - set(value) = setCount(value.toInt()) - - override val maxStackSize get() = ImpreciseFraction(stack.maxStackSize) - - override val isEmpty: Boolean = stack.isEmpty - override val partitionKey: Long = partitionKey(stack.item) - override fun sameItem(other: IStorageStack): Boolean { if (this === other) return true if (other is ItemStackWrapper) - return ItemStack.isSameItemSameTags(stack, other.stack) + return ItemStack.isSameItemSameTags(item, other.item) return false } + fun sameItem(other: ItemStack): Boolean { + return ItemStack.isSameItemSameTags(item, other) + } + companion object { @JvmField val EMPTY = ItemStackWrapper(ItemStack.EMPTY) - fun partitionKey(item: Item) = (ForgeRegistries.ITEMS as? ForgeRegistry)?.getID(item)?.toLong() ?: System.identityHashCode(item).toLong() + fun longHashCode(item: Item) = (ForgeRegistries.ITEMS as? ForgeRegistry)?.getID(item)?.toLong() ?: System.identityHashCode(item).toLong() } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/storage/Registry.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/storage/Registry.kt index 30345ea1e..db986c7b3 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/storage/Registry.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/storage/Registry.kt @@ -1,6 +1,7 @@ package ru.dbotthepony.mc.otm.storage import ru.dbotthepony.mc.otm.core.ImpreciseFraction +import ru.dbotthepony.mc.otm.core.Fraction import java.util.IdentityHashMap open class StorageStackType( @@ -10,7 +11,22 @@ open class StorageStackType( /** * Speculated energy required per operation on stack with size of 1 */ - open val energyPerOperation: ImpreciseFraction + open val energyPerOperation: ImpreciseFraction, + + /** + * Whenever is this stack supports fractional part (e.g. 0.5). + * + * Keep in mind fractions are imprecise and can lead to rounding errors. + * [ImpreciseFraction] class attempts to negate most of the issues + * (e.g. 0.1 + 0.2 eventually getting its 0....4 part into whole part), + * but that is about it. + * + * On design side note, storage system could have been using [Fraction], but there is an issue: + * they are **precise**. Under precise, means that anything that continuously divide/multiply them + * they become more and more "irrational", greatly slowing down operations. Worst case scenario: + * value is getting divided by [Long.MAX_VALUE] again and again, creating insanely huge divisor. + */ + open val fractional: Boolean ) { open fun energyPerOperation(stack: T): ImpreciseFraction { return stack.count * energyPerOperation @@ -32,9 +48,17 @@ object StorageRegistry { return type } + /** + * Refer to [StorageStackType] for explanation of flags. + */ @JvmStatic - fun register(identity: Class, empty: T, energyPerOperation: ImpreciseFraction): StorageStackType { - return register(StorageStackType(identity, empty, energyPerOperation)) as StorageStackType + fun register( + identity: Class, + empty: T, + energyPerOperation: ImpreciseFraction, + fractional: Boolean + ): StorageStackType { + return register(StorageStackType(identity, empty, energyPerOperation, fractional)) as StorageStackType } @JvmStatic diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/storage/VirtualComponent.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/storage/VirtualComponent.kt index 5c733ba9f..4a2e09f2d 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/storage/VirtualComponent.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/storage/VirtualComponent.kt @@ -119,7 +119,7 @@ open class VirtualComponent(type: StorageStackType) : ISto @Suppress("unchecked_cast") override fun addStack(stack: T, id: UUID, provider: IStorageView) { check(!remoteByUUID.containsKey(id)) { "Already tracking tuple with id $id" } - val items = partitions.computeIfAbsent(stack.partitionKey, Long2ObjectFunction { ArrayList() }) + val items = partitions.computeIfAbsent(stack.longHashCode, Long2ObjectFunction { ArrayList() }) var local: LocalTuple? = null var oldCount = ImpreciseFraction.ZERO @@ -172,7 +172,7 @@ open class VirtualComponent(type: StorageStackType) : ISto override fun removeStack(stack: T, id: UUID) { val tuple = remoteByUUID[id] ?: throw IllegalStateException("No such tuple with id $id") - val key = tuple.local.stack.partitionKey + val key = tuple.local.stack.longHashCode tuple.local.stack.shrink(tuple.obj.count) tuple.local.tuples.remove(tuple) @@ -219,10 +219,13 @@ open class VirtualComponent(type: StorageStackType) : ISto if (tuple == null || amount.isZero) return this.storageType.empty - if (amount <= ImpreciseFraction.MINUS_ONE) + if (!storageType.fractional) + amount = amount.floor() + + if (!amount.isPositive) amount = tuple.stack.maxStackSize ?: tuple.stack.count - val toExtract = tuple.stack.count.min(amount) + val toExtract = tuple.stack.count.coerceAtMost(amount) var extracted = ImpreciseFraction.ZERO val copy = tuple.stack.copy() as T