Revise storage system to rely solely on ImpreciseFraction

This commit is contained in:
DBotThePony 2022-03-27 13:16:07 +07:00
parent ecc7d6e26d
commit 917faffcf9
Signed by: DBot
GPG Key ID: DCC23B5715498507
22 changed files with 460 additions and 142 deletions

View File

@ -98,7 +98,7 @@ public final class OverdriveThatMatters {
private void setup(final FMLCommonSetupEvent event) { private void setup(final FMLCommonSetupEvent event) {
MatteryNetworking.register(); 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")) { if (ModList.get().isLoaded("mekanism")) {
MinecraftForge.EVENT_BUS.register(QIOKt.class); MinecraftForge.EVENT_BUS.register(QIOKt.class);

View File

@ -198,7 +198,7 @@ class EnergyCounterBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : Mat
val ioLimit = ioLimit val ioLimit = ioLimit
if (ioLimit != null) { if (ioLimit != null) {
diff = it.extractEnergy(howMuch.min(ioLimit), simulate) diff = it.extractEnergy(howMuch.coerceAtMost(ioLimit), simulate)
} else { } else {
diff = it.extractEnergy(howMuch, simulate) diff = it.extractEnergy(howMuch, simulate)
} }
@ -230,7 +230,7 @@ class EnergyCounterBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : Mat
val ioLimit = ioLimit val ioLimit = ioLimit
if (ioLimit != null) { if (ioLimit != null) {
diff = it.receiveEnergy(howMuch.min(ioLimit), simulate) diff = it.receiveEnergy(howMuch.coerceAtMost(ioLimit), simulate)
} else { } else {
diff = it.receiveEnergy(howMuch, simulate) diff = it.receiveEnergy(howMuch, simulate)
} }

View File

@ -264,7 +264,7 @@ class MatterBottlerBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) :
if (workFlow) { if (workFlow) {
if (matter.storedMatter < MATTER_EXCHANGE_RATE && graph != null) { if (matter.storedMatter < MATTER_EXCHANGE_RATE && graph != null) {
val extracted = graph.extractMatter( 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) { if (extracted > ImpreciseFraction.ZERO) {
@ -277,7 +277,7 @@ class MatterBottlerBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) :
val energyExtracted = energy.extractEnergyInner(ENERGY_CONSUMPTION, true) val energyExtracted = energy.extractEnergyInner(ENERGY_CONSUMPTION, true)
if (!energyExtracted.isZero) { 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) { if (!matter.isZero) {
energy.extractEnergyInner(ENERGY_CONSUMPTION * matter / MATTER_EXCHANGE_RATE, false) 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) val energyExtracted = energy.extractEnergyInner(ENERGY_CONSUMPTION, true)
if (!energyExtracted.isZero) { 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) { if (!matter.isZero) {
this.energy.extractEnergyInner(ENERGY_CONSUMPTION * matter / MATTER_EXCHANGE_RATE,false) this.energy.extractEnergyInner(ENERGY_CONSUMPTION * matter / MATTER_EXCHANGE_RATE,false)

View File

@ -159,7 +159,7 @@ class MatterReplicatorBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) :
// в тик требуется меньше материи, чем её может хранить репликатор // в тик требуется меньше материи, чем её может хранить репликатор
// примем из сети недостающее количество бака материи, или 200 тиков репликации, что меньше // примем из сети недостающее количество бака материи, или 200 тиков репликации, что меньше
val toExtract = val toExtract =
matter.missingMatter.min(drainPerTick.times(DRAIN_MULT)) matter.missingMatter.coerceAtMost(drainPerTick.times(DRAIN_MULT))
val drain = graph.extractMatter(toExtract, true) val drain = graph.extractMatter(toExtract, true)

View File

@ -37,8 +37,20 @@ import java.util.*
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
private class SlotTuple(val slot: Int, val stack: ItemStack) 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<ItemStackWrapper> {
val children = Int2ObjectAVLTreeMap<SlotTuple>() val children = Int2ObjectAVLTreeMap<SlotTuple>()
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<ItemStackWrapper> { private class ItemHandlerComponent(private val parent: IItemHandler) : IStorageComponent<ItemStackWrapper> {
@ -77,14 +89,24 @@ private class ItemHandlerComponent(private val parent: IItemHandler) : IStorageC
if (scannedMap.stack.count.isPositive) { if (scannedMap.stack.count.isPositive) {
for (listener in listeners) { for (listener in listeners) {
listener.changeStack(scannedMap.stack, scannedMap.id, count) listener.changeStack(scannedMap, count)
} }
} else { } else {
for (listener in listeners) { for (listener in listeners) {
listener.removeStack(scannedMap.stack, scannedMap.id) listener.removeStack(scannedMap)
} }
index.remove(scannedMap.id) 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) { private fun addTracked(slot: Int, stack: ItemStack) {
check(scannedMap[slot] == null) { "Already tracking slot $slot" } 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 var tuple: TrackedTuple? = null
for (storedTuple in listing) { for (storedTuple in listing) {
if (ItemStack.isSameItemSameTags(stack, storedTuple.stack.stack)) { if (storedTuple.stack.sameItem(stack)) {
tuple = storedTuple tuple = storedTuple
break break
} }
@ -118,7 +140,7 @@ private class ItemHandlerComponent(private val parent: IItemHandler) : IStorageC
var oldCount = ImpreciseFraction.ZERO var oldCount = ImpreciseFraction.ZERO
if (added) { if (added) {
val storageStack = ItemStackWrapper(stack.copy()) val storageStack = ItemStackWrapper(stack)
tuple = TrackedTuple(storageStack, UUID.randomUUID()) tuple = TrackedTuple(storageStack, UUID.randomUUID())
index[tuple.id] = tuple index[tuple.id] = tuple
listing.add(tuple) listing.add(tuple)
@ -232,9 +254,9 @@ private class ItemHandlerComponent(private val parent: IItemHandler) : IStorageC
if (!amount.isPositive) if (!amount.isPositive)
return ItemStackWrapper.EMPTY return ItemStackWrapper.EMPTY
val intAmount = amount.toInt() val intAmount = amount.toLong()
val tuple = index[id] ?: return ItemStackWrapper.EMPTY val tuple = index[id] ?: return ItemStackWrapper.EMPTY
var totalExtracted = 0 var totalExtracted = 0L
val iter = tuple.children.values.iterator() val iter = tuple.children.values.iterator()
val listCopy = Array(tuple.children.values.size) { val listCopy = Array(tuple.children.values.size) {
@ -244,14 +266,14 @@ private class ItemHandlerComponent(private val parent: IItemHandler) : IStorageC
val copy = tuple.stack.copy() val copy = tuple.stack.copy()
for (stack in listCopy) { 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) { if (extracted.isEmpty) {
// dummy condition // dummy condition
continue continue
} else if (ItemStack.isSameItemSameTags(extracted, tuple.stack.stack)) { } else if (tuple.stack.sameItem(extracted)) {
if (!simulate) { 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 totalExtracted += extracted.count
@ -269,11 +291,11 @@ private class ItemHandlerComponent(private val parent: IItemHandler) : IStorageC
} }
} }
if (totalExtracted == 0) { if (totalExtracted == 0L) {
return ItemStackWrapper.EMPTY return ItemStackWrapper.EMPTY
} }
copy.setCount(totalExtracted) copy.count = ImpreciseFraction(totalExtracted)
return copy return copy
} }

View File

@ -74,7 +74,7 @@ sealed class ItemEnergyStorageImpl(
val maxOutput = maxOutput val maxOutput = maxOutput
if (maxOutput != null) { if (maxOutput != null) {
howMuch = howMuch.min(maxOutput) howMuch = howMuch.coerceAtMost(maxOutput)
} }
val batteryLevel = batteryLevel val batteryLevel = batteryLevel
@ -101,7 +101,7 @@ sealed class ItemEnergyStorageImpl(
val maxInput = maxInput val maxInput = maxInput
if (maxInput != null) { if (maxInput != null) {
howMuch = howMuch.min(maxInput) howMuch = howMuch.coerceAtMost(maxInput)
} }
val batteryLevel = batteryLevel val batteryLevel = batteryLevel
@ -109,7 +109,7 @@ sealed class ItemEnergyStorageImpl(
if (batteryLevel >= maxBatteryLevel) if (batteryLevel >= maxBatteryLevel)
return ImpreciseFraction.ZERO return ImpreciseFraction.ZERO
val newLevel = (batteryLevel + howMuch).min(maxBatteryLevel) val newLevel = (batteryLevel + howMuch).coerceAtMost(maxBatteryLevel)
val diff = (newLevel - batteryLevel) val diff = (newLevel - batteryLevel)
if (!simulate && batteryLevel != newLevel) { if (!simulate && batteryLevel != newLevel) {
@ -173,7 +173,7 @@ sealed class BlockEnergyStorageImpl constructor(
val maxOutput = maxOutput val maxOutput = maxOutput
if (maxOutput != null) { if (maxOutput != null) {
howMuch = howMuch.min(maxOutput) howMuch = howMuch.coerceAtMost(maxOutput)
} }
if (!batteryLevel.isPositive) if (!batteryLevel.isPositive)
@ -199,13 +199,13 @@ sealed class BlockEnergyStorageImpl constructor(
val maxInput = maxInput val maxInput = maxInput
if (maxInput != null) { if (maxInput != null) {
howMuch = howMuch.min(maxInput) howMuch = howMuch.coerceAtMost(maxInput)
} }
if (batteryLevel >= maxBatteryLevel) if (batteryLevel >= maxBatteryLevel)
return ImpreciseFraction.ZERO return ImpreciseFraction.ZERO
val newLevel = (batteryLevel + howMuch).min(maxBatteryLevel) val newLevel = (batteryLevel + howMuch).coerceAtMost(maxBatteryLevel)
val diff = (newLevel - batteryLevel) val diff = (newLevel - batteryLevel)
if (!simulate && batteryLevel != newLevel) { if (!simulate && batteryLevel != newLevel) {

View File

@ -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 received += new - battery
if (!simulate) { if (!simulate) {

View File

@ -40,10 +40,10 @@ abstract class AbstractMatteryDrive<T : IStorageStack> @JvmOverloads constructor
@Suppress("unchecked_cast") @Suppress("unchecked_cast")
override fun insertStack(stack: T, simulate: Boolean): T { 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 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) { for (state in listing) {
if (state.stack.sameItem(stack)) { if (state.stack.sameItem(stack)) {
@ -98,10 +98,13 @@ abstract class AbstractMatteryDrive<T : IStorageStack> @JvmOverloads constructor
var amount = amount var amount = amount
val get = storedByID[id] ?: return storageType.empty 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 = get.stack.maxStackSize ?: get.stack.count
amount = amount.min(get.stack.count) amount = amount.coerceAtMost(get.stack.count)
if (amount <= ImpreciseFraction.ZERO) if (amount <= ImpreciseFraction.ZERO)
return storageType.empty return storageType.empty
@ -110,8 +113,8 @@ abstract class AbstractMatteryDrive<T : IStorageStack> @JvmOverloads constructor
copy.count = amount copy.count = amount
if (!simulate) { if (!simulate) {
if (amount.compareTo(get.stack.count) == 0) { if (amount == get.stack.count) {
val listing = storedStacks[get.stack.partitionKey]!! val listing = storedStacks[get.stack.longHashCode] ?: throw IllegalStateException("Can't find storage list with partition ${get.stack.longHashCode} for ${get.stack}")
listing.remove(get) listing.remove(get)
storedDifferentStacks-- storedDifferentStacks--
@ -120,7 +123,7 @@ abstract class AbstractMatteryDrive<T : IStorageStack> @JvmOverloads constructor
} }
if (listing.size == 0) { if (listing.size == 0) {
storedStacks.remove(get.stack.partitionKey) storedStacks.remove(get.stack.longHashCode)
} }
} }
@ -128,7 +131,7 @@ abstract class AbstractMatteryDrive<T : IStorageStack> @JvmOverloads constructor
val oldCount = get.stack.count val oldCount = get.stack.count
get.stack.shrink(amount) get.stack.shrink(amount)
if (!get.stack.count.isZero) { if (get.stack.count.isPositive) {
for (listener in listeners) { for (listener in listeners) {
listener.changeStack(get.stack, get.id, oldCount) listener.changeStack(get.stack, get.id, oldCount)
} }
@ -182,13 +185,13 @@ abstract class AbstractMatteryDrive<T : IStorageStack> @JvmOverloads constructor
val stack = deserializeStack(entry) val stack = deserializeStack(entry)
if (stack != null) { if (stack != null) {
storedCount = storedCount.plus(stack.count) storedCount += stack.count
storedDifferentStacks++ storedDifferentStacks++
val id = entry.getLongArray("id") val id = entry.getLongArray("id")
val tuple = StorageTuple(if (id.size == 2) UUID(id[0], id[1]) else UUID.randomUUID(), stack) 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 storedByID[tuple.id] = tuple
} }
} }

View File

@ -28,12 +28,12 @@ class ItemMatteryDrive : AbstractMatteryDrive<ItemStackWrapper>, IItemMatteryDri
override fun serializeStack(item: IStorageTuple<ItemStackWrapper>): CompoundTag? { override fun serializeStack(item: IStorageTuple<ItemStackWrapper>): CompoundTag? {
val tag = CompoundTag() val tag = CompoundTag()
val location = item.stack.stack.item.registryName!!.toString() val location = item.stack.registryName.toString()
tag["item"] = location 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) { if (itag != null) {
tag["data"] = itag tag["data"] = itag
@ -56,32 +56,22 @@ class ItemMatteryDrive : AbstractMatteryDrive<ItemStackWrapper>, IItemMatteryDri
} }
override fun findItems(item: Item): List<IStorageTuple<ItemStackWrapper>> { override fun findItems(item: Item): List<IStorageTuple<ItemStackWrapper>> {
val list = storedStacks[ItemStackWrapper.partitionKey(item)] val list = storedStacks[ItemStackWrapper.longHashCode(item)]
return if (list != null) java.util.List.copyOf(list) else emptyList() return if (list != null) java.util.List.copyOf(list) else emptyList()
} }
override fun findItems(stack: ItemStack): List<IStorageTuple<ItemStackWrapper>> { override fun findItems(stack: ItemStack): List<IStorageTuple<ItemStackWrapper>> {
val list = storedStacks[ItemStackWrapper.partitionKey(stack.item)] ?: return emptyList() val list = storedStacks[ItemStackWrapper.longHashCode(stack.item)] ?: return emptyList()
var amount = 0 val buildList = ArrayList<IStorageTuple<ItemStackWrapper>>()
for (_stack in list) { for (_stack in list) {
if (ItemStack.tagMatches(_stack.stack.stack, stack)) { if (_stack.stack.sameItem(stack)) {
amount++ buildList.add(_stack)
} }
} }
val build_list = ArrayList<IStorageTuple<ItemStackWrapper>>(amount) return buildList
var i = 0
for (_stack in list) {
if (ItemStack.tagMatches(_stack.stack.stack, stack)) {
build_list[i] = _stack
i++
}
}
return build_list
} }
companion object { companion object {

View File

@ -45,9 +45,9 @@ open class MatterHandlerImpl @JvmOverloads constructor(
val new: ImpreciseFraction val new: ImpreciseFraction
if (maxReceive == null) { if (maxReceive == null) {
new = (storedMatter + howMuch).min(maxStoredMatter) new = (storedMatter + howMuch).coerceAtMost(maxStoredMatter)
} else { } else {
new = (storedMatter + howMuch.min(maxReceive)).min(maxStoredMatter) new = (storedMatter + howMuch.coerceAtMost(maxReceive)).coerceAtMost(maxStoredMatter)
} }
val diff = new - storedMatter val diff = new - storedMatter
@ -73,7 +73,7 @@ open class MatterHandlerImpl @JvmOverloads constructor(
if (maxExtract == null) { if (maxExtract == null) {
new = (storedMatter - howMuch).moreThanZero() new = (storedMatter - howMuch).moreThanZero()
} else { } else {
new = (storedMatter - howMuch.min(maxExtract)).moreThanZero() new = (storedMatter - howMuch.coerceAtMost(maxExtract)).moreThanZero()
} }
val diff = storedMatter - new val diff = storedMatter - new

View File

@ -6,7 +6,6 @@ import it.unimi.dsi.fastutil.objects.Object2ObjectFunction
import mekanism.api.math.MathUtils import mekanism.api.math.MathUtils
import mekanism.common.content.qio.QIOFrequency import mekanism.common.content.qio.QIOFrequency
import mekanism.common.content.qio.QIOFrequency.QIOItemTypeData import mekanism.common.content.qio.QIOFrequency.QIOItemTypeData
import mekanism.common.content.transporter.TransporterManager
import mekanism.common.lib.frequency.Frequency import mekanism.common.lib.frequency.Frequency
import mekanism.common.lib.inventory.HashedItem import mekanism.common.lib.inventory.HashedItem
import mekanism.common.tile.qio.TileEntityQIODriveArray 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.MatteryCapability
import ru.dbotthepony.mc.otm.capability.isMekanismLoaded import ru.dbotthepony.mc.otm.capability.isMekanismLoaded
import ru.dbotthepony.mc.otm.core.ImpreciseFraction 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.BasicStorageGraphNode
import ru.dbotthepony.mc.otm.graph.storage.StorageNetworkGraph import ru.dbotthepony.mc.otm.graph.storage.StorageNetworkGraph
import ru.dbotthepony.mc.otm.storage.* import ru.dbotthepony.mc.otm.storage.*
@ -58,13 +58,13 @@ private class QIOFrequencyAccess(val parent: QIOFrequency) : IStorageComponent<I
override fun insertStack(stack: ItemStackWrapper, simulate: Boolean): ItemStackWrapper { override fun insertStack(stack: ItemStackWrapper, simulate: Boolean): ItemStackWrapper {
// Because there is no simulate method on QIO array, we have to simulate it by ourselves. // Because there is no simulate method on QIO array, we have to simulate it by ourselves.
val hash = HashedItem.create(stack.stack) val hash = HashedItem.create(stack.item)
if (!simulate) { if (!simulate) {
//val used = TransporterManager.getToUse(stack.stack, parent.addItem(stack.stack)) //val used = TransporterManager.getToUse(stack.stack, parent.addItem(stack.stack))
val used = parent.addItem(stack.stack) val used = parent.addItem(stack.stack)
if (used.count == stack.stack.count) { if (used.count == stack.count.toInt()) {
return stack return stack
} }
@ -81,7 +81,7 @@ private class QIOFrequencyAccess(val parent: QIOFrequency) : IStorageComponent<I
} }
val inserted = stack.copy() val inserted = stack.copy()
inserted.stack.count = MathUtils.clampToInt(parent.totalItemCountCapacity - parent.totalItemCount).coerceAtMost(stack.stack.count) inserted.count = (parent.totalItemCountCapacity - parent.totalItemCount).toImpreciseFraction().coerceAtMost(stack.count)
return inserted return inserted
} }
@ -97,7 +97,7 @@ private class QIOFrequencyAccess(val parent: QIOFrequency) : IStorageComponent<I
local = index[id] ?: return ItemStackWrapper.EMPTY // unexpected... local = index[id] ?: return ItemStackWrapper.EMPTY // unexpected...
if (simulate) { if (simulate) {
return local.stack.copy().also { it.stack.count = it.stack.count.coerceAtMost(amount.toInt()) } return local.stack.copy().also { it.count = it.count.coerceAtMost(amount) }
} }
val removed = parent.removeByType(local.mekanismItem, amount.toInt()) val removed = parent.removeByType(local.mekanismItem, amount.toInt())
@ -106,7 +106,7 @@ private class QIOFrequencyAccess(val parent: QIOFrequency) : IStorageComponent<I
return ItemStackWrapper.EMPTY return ItemStackWrapper.EMPTY
} }
val copy = ItemStackWrapper(removed.copy()) val copy = ItemStackWrapper(removed)
if (local.stack.count > copy.count) { if (local.stack.count > copy.count) {
// expecting stack to be still present in QIO storage grid // expecting stack to be still present in QIO storage grid
@ -147,16 +147,16 @@ private class QIOFrequencyAccess(val parent: QIOFrequency) : IStorageComponent<I
if (local != null) { if (local != null) {
local.mark = mark local.mark = mark
if (local.stack.stack.count != MathUtils.clampToInt(value.count)) { if (local.stack.count.toLong() != value.count) {
val oldCount = local.stack.count val oldCount = local.stack.count
local.stack.stack.count = MathUtils.clampToInt(value.count) local.stack.count = value.count.toImpreciseFraction()
for (listener in listeners) { for (listener in listeners) {
listener.changeStack(local, oldCount) listener.changeStack(local, oldCount)
} }
} }
} else { } else {
val tuple = QIOTuple(at, ItemStackWrapper(at.stack.copy().also { it.count = MathUtils.clampToInt(value.count) }), UUID.randomUUID(), mark) val tuple = QIOTuple(at, ItemStackWrapper(at.stack).also { it.count = value.count.toImpreciseFraction() }, UUID.randomUUID(), mark)
index[tuple.id] = tuple index[tuple.id] = tuple
for (listener in listeners) { for (listener in listeners) {

View File

@ -1,8 +1,12 @@
@file:Suppress("unused")
package ru.dbotthepony.mc.otm.core package ru.dbotthepony.mc.otm.core
import net.minecraft.core.BlockPos import net.minecraft.core.BlockPos
import net.minecraft.core.Direction import net.minecraft.core.Direction
import net.minecraft.core.Vec3i import net.minecraft.core.Vec3i
import java.math.BigDecimal
operator fun BlockPos.plus(direction: Vec3i): BlockPos = this.offset(direction) operator fun BlockPos.plus(direction: Vec3i): BlockPos = this.offset(direction)
operator fun BlockPos.plus(direction: Direction): BlockPos = this.offset(direction.normal) operator fun BlockPos.plus(direction: Direction): BlockPos = this.offset(direction.normal)
@ -20,3 +24,188 @@ operator fun Vec3i.times(double: Double): Vector = Vector(x * double, y * double
fun BlockPos.asVector(): Vector { fun BlockPos.asVector(): Vector {
return Vector(x + 0.5, y + 0.5, z + 0.5) return Vector(x + 0.5, y + 0.5, z + 0.5)
} }
/**
* Performs type check+cast and sums two numbers.
*
* Returns the same type as on input (adding Double to Float will not return Double, etc.)
*/
fun Number.dynPlus(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 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")
}
}

View File

@ -10,8 +10,6 @@ import java.math.BigInteger
import java.math.MathContext import java.math.MathContext
import java.math.RoundingMode import java.math.RoundingMode
private val IMPRECISE_CONTEXT = MathContext(17)
private fun powScale(int: Int): BigInteger { private fun powScale(int: Int): BigInteger {
if (int <= 0) if (int <= 0)
return BigInteger.ONE return BigInteger.ONE
@ -118,6 +116,7 @@ private val GCDs = arrayOf(
private val COMPACT_THRESHOLD = BigInteger("1000000000000000000000") private val COMPACT_THRESHOLD = BigInteger("1000000000000000000000")
@Suppress("NAME_SHADOWING")
private fun compactTwo(value1: BigInteger, value2: BigInteger, compact: Boolean = true): Fraction { private fun compactTwo(value1: BigInteger, value2: BigInteger, compact: Boolean = true): Fraction {
when (value1.signum()) { when (value1.signum()) {
0 -> return Fraction(value1, value2, compact = compact) 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) private data class MagnitudeCrunchResult(val value: BigInteger, val mag: Int)
@Suppress("NAME_SHADOWING")
private fun magnitude(value: BigInteger): MagnitudeCrunchResult { private fun magnitude(value: BigInteger): MagnitudeCrunchResult {
if (isZero(value)) if (isZero(value))
return MagnitudeCrunchResult(value, 0) return MagnitudeCrunchResult(value, 0)
@ -238,9 +238,12 @@ private fun magnitude(value: BigInteger): MagnitudeCrunchResult {
return MagnitudeCrunchResult(value, mag) return MagnitudeCrunchResult(value, mag)
} }
@JvmRecord
@Suppress("unused") @Suppress("unused")
data class Fraction @JvmOverloads constructor(@JvmField val value: BigInteger, @JvmField val divisor: BigInteger = BigInteger.ONE, @JvmField val compact: Boolean = true) : Comparable<Fraction> { class Fraction @JvmOverloads constructor(
val value: BigInteger,
val divisor: BigInteger = BigInteger.ONE,
val compact: Boolean = true
) : Number(), Comparable<Fraction> {
@JvmOverloads constructor(value: Long, compact: Boolean = true) : this(BigInteger.valueOf(value), compact = compact) @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: Int, compact: Boolean = true) : this(BigInteger.valueOf(value.toLong()), compact = compact)
@JvmOverloads constructor(value: Float, compact: Boolean = true) : this(BigDecimal(value.toString()), 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 if (isNaN()) return Float.NaN
return (value / divisor).toFloat() + ((value % divisor).toFloat() / divisor.toFloat()) return (value / divisor).toFloat() + ((value % divisor).toFloat() / divisor.toFloat())
} }
fun toDouble(): Double { override fun toDouble(): Double {
if (isNaN()) return Double.NaN if (isNaN()) return Double.NaN
return (value / divisor).toDouble() + ((value % divisor).toDouble() / divisor.toDouble()) 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 a = divisor.toDouble()
val b = div[1].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]) 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") if (isNaN()) throw ArithmeticException("Fraction is not a number")
return (value / divisor).toInt() return (value / divisor).toInt()
} }
fun toLong(): Long { override fun toLong(): Long {
if (isNaN()) throw ArithmeticException("Fraction is not a number") if (isNaN()) throw ArithmeticException("Fraction is not a number")
return (value / divisor).toLong() 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 @JvmOverloads
fun toBigDecimal(context: MathContext = DEFAULT_MATH_CONTEXT): BigDecimal { fun toBigDecimal(context: MathContext = DEFAULT_MATH_CONTEXT): BigDecimal {
if (isNaN()) throw ArithmeticException("Fraction is not a number") if (isNaN()) throw ArithmeticException("Fraction is not a number")

View File

@ -68,6 +68,11 @@ private fun bytesToLongBE(
) )
} }
/**
* Constant value which represent edge of meaningful bits.
*
* Equals to 0.000000000001
*/
const val EPSILON = 0.000000000001 const val EPSILON = 0.000000000001
fun weakEqualDoubles(a: Double, b: Double): Boolean { 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_CONTEXT = MathContext(MEANINGFUL_BITS)
private val MEANINGFUL_BITS_BI = BigInteger.valueOf(MEANINGFUL_BITS_LONG) 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") @Suppress("unused")
class ImpreciseFraction @JvmOverloads constructor(whole: BigInteger, decimal: Double = 0.0) : Comparable<ImpreciseFraction> { class ImpreciseFraction @JvmOverloads constructor(whole: BigInteger, decimal: Double = 0.0) : Number(), Comparable<ImpreciseFraction> {
@JvmOverloads constructor(whole: Byte, decimal: Double = 0.0) : this(BigInteger.valueOf(whole.toLong()), decimal) @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: 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) @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: Double) : this(BigInteger.valueOf((value - value % 1.0).toLong()), value % 1.0)
constructor(value: String) : this(BigDecimal(value)) constructor(value: String) : this(BigDecimal(value))
/**
* Inexact fractional part of this fraction
*/
val decimal: Double val decimal: Double
/**
* Exact whole part of this fraction
*/
val whole: BigInteger val whole: BigInteger
init { init {
@ -136,8 +160,8 @@ class ImpreciseFraction @JvmOverloads constructor(whole: BigInteger, decimal: Do
decimal = frac decimal = frac
} }
// дробная часть равна нулю // дробная часть равна или очень близка к нулю
if (decimal == 0.0) { if (weakEqualDoubles(decimal, 0.0)) {
if (!isZero(whole)) { if (!isZero(whole)) {
this.decimal = 0.0 this.decimal = 0.0
this.whole = whole this.whole = whole
@ -379,6 +403,11 @@ class ImpreciseFraction @JvmOverloads constructor(whole: BigInteger, decimal: Do
return sign return sign
} }
// exactly zero
if (decimal == 0.0)
return if (1.0 / decimal > 0) 1 else -1
// inexactly zero
if (weakEqualDoubles(decimal, 0.0)) if (weakEqualDoubles(decimal, 0.0))
return 0 return 0
@ -458,31 +487,7 @@ class ImpreciseFraction @JvmOverloads constructor(whole: BigInteger, decimal: Do
return ZERO return ZERO
} }
fun max(vararg others: ImpreciseFraction): ImpreciseFraction { override fun toInt(): Int {
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 {
if (whole > BI_INT_MAX) { if (whole > BI_INT_MAX) {
return Int.MAX_VALUE return Int.MAX_VALUE
} else if (whole < BI_INT_MIN) { } else if (whole < BI_INT_MIN) {
@ -499,14 +504,36 @@ class ImpreciseFraction @JvmOverloads constructor(whole: BigInteger, decimal: Do
buff.writeBytes(longToBytesBE(decimal.toBits())) buff.writeBytes(longToBytesBE(decimal.toBits()))
} }
fun toFloat(): Float { override fun toFloat(): Float {
return whole.toFloat() + decimal.toFloat() return whole.toFloat() + decimal.toFloat()
} }
fun toDouble(): Double { override fun toDouble(): Double {
return whole.toDouble() + decimal 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 { fun toFraction(): Fraction {
if (isZero) { if (isZero) {
return Fraction.ZERO return Fraction.ZERO

View File

@ -61,7 +61,7 @@ class MatterCapacitorItem : Item {
override fun receiveMatterInner(howMuch: ImpreciseFraction, simulate: Boolean): ImpreciseFraction { override fun receiveMatterInner(howMuch: ImpreciseFraction, simulate: Boolean): ImpreciseFraction {
if (isCreative) return howMuch 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()) val diff = new.minus(matter())
if (!simulate) { if (!simulate) {
@ -77,7 +77,7 @@ class MatterCapacitorItem : Item {
override fun extractMatterInner(howMuch: ImpreciseFraction, simulate: Boolean): ImpreciseFraction { override fun extractMatterInner(howMuch: ImpreciseFraction, simulate: Boolean): ImpreciseFraction {
if (isCreative) return howMuch 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) val diff = matter().minus(new)
if (!simulate) { if (!simulate) {

View File

@ -58,7 +58,7 @@ class MatterDustItem : Item(Properties().tab(OverdriveThatMatters.INSTANCE.CREAT
if (matterThis >= MAX_MATTER_IN_ITEM) if (matterThis >= MAX_MATTER_IN_ITEM)
return ImpreciseFraction.ZERO 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 val diff = newMatter - matterThis
if (!simulate) if (!simulate)

View File

@ -112,7 +112,7 @@ class PlasmaWeaponEnergy(val itemStack: ItemStack, private val innerCapacity: Im
return totalReceived return totalReceived
} }
val newEnergy = (innerBatteryLevel - howMuch).min(innerCapacity) val newEnergy = (innerBatteryLevel - howMuch).coerceAtMost(innerCapacity)
val diff = newEnergy - innerBatteryLevel val diff = newEnergy - innerBatteryLevel
if (!simulate) { if (!simulate) {

View File

@ -165,8 +165,8 @@ open class NetworkedItemView(val ply: Player, val menu: MatteryMenu, val remote:
if (!extracted.isEmpty) { if (!extracted.isEmpty) {
val (_, remaining) = menu.quickMoveToInventory(extracted.stack, false) val (_, remaining) = menu.quickMoveToInventory(extracted.stack, false)
if (remaining.count != extracted.stack.count) { if (remaining.count != extracted.count.toInt()) {
provider.extractStack(state.upstreamId, extracted.stack.count - remaining.count, false) 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 val amount = carried.count
if (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 menu.carried = stack
MatteryNetworking.send(ply as ServerPlayer, SetCarriedPacket(menu.carried)) MatteryNetworking.send(ply as ServerPlayer, SetCarriedPacket(menu.carried))
menu.setRemoteCarried(menu.carried.copy()) menu.setRemoteCarried(menu.carried.copy())
@ -202,7 +202,7 @@ open class NetworkedItemView(val ply: Player, val menu: MatteryMenu, val remote:
if (action == ClickAction.PRIMARY) if (action == ClickAction.PRIMARY)
state.stack.maxStackSize state.stack.maxStackSize
else else
Math.max(1, state.stack.maxStackSize / 2) 1.coerceAtLeast(state.stack.maxStackSize / 2)
val extracted = provider.extractStack(state.upstreamId!!, amount, false) val extracted = provider.extractStack(state.upstreamId!!, amount, false)
menu.carried = extracted.stack menu.carried = extracted.stack

View File

@ -7,6 +7,18 @@ import net.minecraftforge.registries.ForgeRegistry
interface IStorageStack { interface IStorageStack {
fun copy(): 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 var count: ImpreciseFraction
val isEmpty: Boolean val isEmpty: Boolean
@ -17,15 +29,17 @@ interface IStorageStack {
val maxStackSize: ImpreciseFraction? get() = null 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. * 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 * This property **must** be equal for two entries if [sameItem] returns true.
* entries if [sameItem] returns true. This value should not change. * This value should not change.
* *
* A good example of key is [ForgeRegistry.getID]. * 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. * [other] is the stack to compare.

View File

@ -6,40 +6,71 @@ import net.minecraftforge.registries.ForgeRegistries
import net.minecraftforge.registries.ForgeRegistry import net.minecraftforge.registries.ForgeRegistry
import ru.dbotthepony.mc.otm.core.ImpreciseFraction 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 { override fun copy(): ItemStackWrapper {
return ItemStackWrapper(stack.copy()) return ItemStackWrapper(this)
} }
fun setCount(value: Int) { override val maxStackSize get() = ImpreciseFraction(item.maxStackSize)
stack.count = value.coerceAtLeast(0)
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 { override fun sameItem(other: IStorageStack): Boolean {
if (this === other) if (this === other)
return true return true
if (other is ItemStackWrapper) if (other is ItemStackWrapper)
return ItemStack.isSameItemSameTags(stack, other.stack) return ItemStack.isSameItemSameTags(item, other.item)
return false return false
} }
fun sameItem(other: ItemStack): Boolean {
return ItemStack.isSameItemSameTags(item, other)
}
companion object { companion object {
@JvmField @JvmField
val EMPTY = ItemStackWrapper(ItemStack.EMPTY) val EMPTY = ItemStackWrapper(ItemStack.EMPTY)
fun partitionKey(item: Item) = (ForgeRegistries.ITEMS as? ForgeRegistry<Item>)?.getID(item)?.toLong() ?: System.identityHashCode(item).toLong() fun longHashCode(item: Item) = (ForgeRegistries.ITEMS as? ForgeRegistry<Item>)?.getID(item)?.toLong() ?: System.identityHashCode(item).toLong()
} }
} }

View File

@ -1,6 +1,7 @@
package ru.dbotthepony.mc.otm.storage package ru.dbotthepony.mc.otm.storage
import ru.dbotthepony.mc.otm.core.ImpreciseFraction import ru.dbotthepony.mc.otm.core.ImpreciseFraction
import ru.dbotthepony.mc.otm.core.Fraction
import java.util.IdentityHashMap import java.util.IdentityHashMap
open class StorageStackType<T : IStorageStack>( open class StorageStackType<T : IStorageStack>(
@ -10,7 +11,22 @@ open class StorageStackType<T : IStorageStack>(
/** /**
* Speculated energy required per operation on stack with size of 1 * 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 { open fun energyPerOperation(stack: T): ImpreciseFraction {
return stack.count * energyPerOperation return stack.count * energyPerOperation
@ -32,9 +48,17 @@ object StorageRegistry {
return type return type
} }
/**
* Refer to [StorageStackType] for explanation of flags.
*/
@JvmStatic @JvmStatic
fun <T : IStorageStack> register(identity: Class<T>, empty: T, energyPerOperation: ImpreciseFraction): StorageStackType<T> { fun <T : IStorageStack> register(
return register(StorageStackType(identity, empty, energyPerOperation)) as StorageStackType<T> identity: Class<T>,
empty: T,
energyPerOperation: ImpreciseFraction,
fractional: Boolean
): StorageStackType<T> {
return register(StorageStackType(identity, empty, energyPerOperation, fractional)) as StorageStackType<T>
} }
@JvmStatic @JvmStatic

View File

@ -119,7 +119,7 @@ open class VirtualComponent<T : IStorageStack>(type: StorageStackType<T>) : ISto
@Suppress("unchecked_cast") @Suppress("unchecked_cast")
override fun addStack(stack: T, id: UUID, provider: IStorageView<T>) { override fun addStack(stack: T, id: UUID, provider: IStorageView<T>) {
check(!remoteByUUID.containsKey(id)) { "Already tracking tuple with id $id" } 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<T>? = null var local: LocalTuple<T>? = null
var oldCount = ImpreciseFraction.ZERO var oldCount = ImpreciseFraction.ZERO
@ -172,7 +172,7 @@ open class VirtualComponent<T : IStorageStack>(type: StorageStackType<T>) : ISto
override fun removeStack(stack: T, id: UUID) { override fun removeStack(stack: T, id: UUID) {
val tuple = remoteByUUID[id] ?: throw IllegalStateException("No such tuple with id $id") 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.stack.shrink(tuple.obj.count)
tuple.local.tuples.remove(tuple) tuple.local.tuples.remove(tuple)
@ -219,10 +219,13 @@ open class VirtualComponent<T : IStorageStack>(type: StorageStackType<T>) : ISto
if (tuple == null || amount.isZero) if (tuple == null || amount.isZero)
return this.storageType.empty 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 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 var extracted = ImpreciseFraction.ZERO
val copy = tuple.stack.copy() as T val copy = tuple.stack.copy() as T