Iterate storage system design once again

This commit is contained in:
DBotThePony 2022-02-26 12:08:17 +07:00
parent da7be875d5
commit 398105f067
Signed by: DBot
GPG Key ID: DCC23B5715498507
19 changed files with 255 additions and 289 deletions

View File

@ -27,8 +27,8 @@ import ru.dbotthepony.mc.otm.matter.MatterRegistryKt;
import ru.dbotthepony.mc.otm.network.MatteryNetworking;
import ru.dbotthepony.mc.otm.registry.*;
import ru.dbotthepony.mc.otm.storage.ItemStackWrapper;
import ru.dbotthepony.mc.otm.storage.StorageObjectRegistry;
import ru.dbotthepony.mc.otm.storage.StorageObjectTuple;
import ru.dbotthepony.mc.otm.storage.StorageRegistry;
import ru.dbotthepony.mc.otm.storage.StorageStackType;
import javax.annotation.ParametersAreNonnullByDefault;
@ -43,7 +43,11 @@ public final class OverdriveThatMatters {
public static OverdriveThatMatters INSTANCE;
public AndroidGui ANDROID_GUI;
public StorageObjectTuple<ItemStackWrapper> ITEM_STORAGE;
private StorageStackType<ItemStackWrapper> ITEM_STORAGE;
public StorageStackType<ItemStackWrapper> ITEM_STORAGE() {
return ITEM_STORAGE;
}
public static ResourceLocation loc(String path) {
return new ResourceLocation(MOD_ID, path);
@ -89,9 +93,7 @@ public final class OverdriveThatMatters {
private void setup(final FMLCommonSetupEvent event) {
MatteryNetworking.register();
// LOGGER.info("Registered network");
ITEM_STORAGE = StorageObjectRegistry.register(ItemStackWrapper.class, ItemStackWrapper.EMPTY, new ImpreciseFraction("3.125"));
ITEM_STORAGE = StorageRegistry.register(ItemStackWrapper.class, ItemStackWrapper.EMPTY, new ImpreciseFraction("3.125"));
}
private void setupClient(final FMLClientSetupEvent event) {

View File

@ -1,34 +0,0 @@
package ru.dbotthepony.mc.otm.storage;
import net.minecraft.MethodsReturnNonnullByDefault;
import ru.dbotthepony.mc.otm.core.Fraction;
import ru.dbotthepony.mc.otm.core.ImpreciseFraction;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.ParametersAreNonnullByDefault;
import java.util.HashMap;
import java.util.Objects;
@ParametersAreNonnullByDefault
@MethodsReturnNonnullByDefault
public class StorageObjectRegistry {
private static final HashMap<Class<? extends IStorageStack>, StorageObjectTuple<? extends IStorageStack>> REGISTRY = new HashMap<>();
public static <T extends IStorageStack> StorageObjectTuple<T> register(Class<T> identity, T empty, ImpreciseFraction energyPerOperation) {
final var tuple = new StorageObjectTuple<>(identity, empty, energyPerOperation);
REGISTRY.put(identity, tuple);
return tuple;
}
@SuppressWarnings("unchecked")
@Nullable
public static <T extends IStorageStack> StorageObjectTuple<T> get(Class<T> identity) {
return (StorageObjectTuple<T>) REGISTRY.get(identity);
}
@Nonnull
public static <T extends IStorageStack> StorageObjectTuple<T> getOrError(Class<T> identity) {
return Objects.requireNonNull(get(identity), "No storage mapping present for " + identity);
}
}

View File

@ -1,21 +0,0 @@
package ru.dbotthepony.mc.otm.storage;
import ru.dbotthepony.mc.otm.core.Fraction;
import ru.dbotthepony.mc.otm.core.ImpreciseFraction;
import javax.annotation.Nonnull;
public record StorageObjectTuple<T extends IStorageStack>(@Nonnull Class<T> identity, @Nonnull T empty, @Nonnull ImpreciseFraction energyPerOperation) {
@Override
public boolean equals(Object obj) {
if (obj instanceof StorageObjectTuple tuple)
return tuple.identity == identity;
return false;
}
@Override
public int hashCode() {
return identity.hashCode();
}
}

View File

@ -36,11 +36,11 @@ class DriveRackBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) :
super.setChanged(slot, new, old)
old.getCapability(MatteryCapability.DRIVE).ifPresent {
cell.computeIfAbsent(it.storageIdentity()) {c -> PoweredVirtualComponent(c, energy)}.remove(it)
cell.computeIfAbsent(it.storageType) {PoweredVirtualComponent(it, energy)}.remove(it)
}
new.getCapability(MatteryCapability.DRIVE).ifPresent {
cell.computeIfAbsent(it.storageIdentity()) {c -> PoweredVirtualComponent(c, energy)}.add(it)
cell.computeIfAbsent(it.storageType) {PoweredVirtualComponent(it, energy)}.add(it)
}
}
}

View File

@ -31,7 +31,7 @@ class DriveViewerBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : Matte
tickOnceServer {
var state = blockState
if (container.getItem(0).getCapability(MatteryCapability.DRIVE).isPresent && energy.batteryLevel >= OverdriveThatMatters.INSTANCE.ITEM_STORAGE.energyPerOperation()) {
if (container.getItem(0).getCapability(MatteryCapability.DRIVE).isPresent && energy.batteryLevel >= OverdriveThatMatters.INSTANCE.ITEM_STORAGE().energyPerOperation) {
state = state.setValue(WorkerState.SEMI_WORKER_STATE, WorkerState.WORKING)
} else {
state = state.setValue(WorkerState.SEMI_WORKER_STATE, WorkerState.IDLE)

View File

@ -1,37 +1,28 @@
package ru.dbotthepony.mc.otm.capability.drive
import ru.dbotthepony.mc.otm.core.Fraction.Companion.deserializeNBT
import ru.dbotthepony.mc.otm.storage.IStorageStack
import it.unimi.dsi.fastutil.longs.Long2ObjectAVLTreeMap
import it.unimi.dsi.fastutil.longs.Long2ObjectFunction
import it.unimi.dsi.fastutil.objects.Object2ObjectAVLTreeMap
import kotlin.jvm.JvmOverloads
import java.util.HashMap
import ru.dbotthepony.mc.otm.storage.IStorageTuple
import java.util.UUID
import ru.dbotthepony.mc.otm.storage.StorageObjectTuple
import ru.dbotthepony.mc.otm.storage.IStorageListener
import ru.dbotthepony.mc.otm.storage.StorageTuple
import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.ListTag
import net.minecraft.nbt.Tag
import ru.dbotthepony.mc.otm.core.Fraction
import ru.dbotthepony.mc.otm.core.ImpreciseFraction
import ru.dbotthepony.mc.otm.ifHas
import ru.dbotthepony.mc.otm.set
import ru.dbotthepony.mc.otm.storage.*
import java.util.ArrayList
import java.util.HashSet
abstract class AbstractMatteryDrive<T : IStorageStack> @JvmOverloads constructor(
override var driveCapacity: ImpreciseFraction,
override val uuid: UUID = UUID.randomUUID(),
var maxDifferentStacks: Int = 0xFFFF
) : IMatteryDrive<T> {
protected val storedStacks = Long2ObjectAVLTreeMap<MutableList<IStorageTuple<T>>>()
protected val storedByID = Object2ObjectAVLTreeMap<UUID, IStorageTuple<T>>()
@JvmField
protected val storedStacks = HashMap<Any, MutableList<IStorageTuple<T>>>()
@JvmField
protected val storedByID = HashMap<UUID, IStorageTuple<T>>()
abstract fun identity(): StorageObjectTuple<T>
override var isDirty = false
set(value) {
if (value != field && value && DrivePool.isLegalAccess()) {
@ -42,24 +33,26 @@ abstract class AbstractMatteryDrive<T : IStorageStack> @JvmOverloads constructor
}
var storedDifferentStacks = 0
protected set
override var storedCount = ImpreciseFraction.ZERO
protected set
@Suppress("unchecked_cast")
override fun insertStack(stack: T, simulate: Boolean): T {
val maxInsert = driveCapacity.minus(storedCount).min(stack.count)
if (maxInsert <= ImpreciseFraction.ZERO) return stack
val listing = storedStacks.computeIfAbsent(stack.partitionKey()) { ArrayList() }
val listing = storedStacks.computeIfAbsent(stack.partitionKey, Long2ObjectFunction { ArrayList() })
for (state in listing) {
if (state.stack().sameItem(stack)) {
if (state.stack.sameItem(stack)) {
if (!simulate) {
state.stack().grow(maxInsert)
state.stack.grow(maxInsert)
storedCount += maxInsert
for (listener in listeners) {
listener.changeObject(state.id(), state.stack().count)
listener.changeStack(state.id, state.stack.count)
}
isDirty = true
@ -84,10 +77,10 @@ abstract class AbstractMatteryDrive<T : IStorageStack> @JvmOverloads constructor
val state = StorageTuple(UUID.randomUUID(), copy)
listing.add(state)
storedByID[state.id()] = state
storedByID[state.id] = state
for (listener in listeners) {
listener.addObject(state.stack(), state.id(), this)
listener.addStack(state.stack, state.id, this)
}
isDirty = true
@ -98,33 +91,35 @@ abstract class AbstractMatteryDrive<T : IStorageStack> @JvmOverloads constructor
return copy
}
@Suppress("unchecked_cast")
override fun extractStack(id: UUID, amount: ImpreciseFraction, simulate: Boolean): T {
@Suppress("NAME_SHADOWING")
var amount = amount
val get = storedByID[id] ?: return identity().empty()
val get = storedByID[id] ?: return storageType.empty
if (amount <= ImpreciseFraction.ZERO)
amount = get.stack.getMaxStackSize().orElse(get.stack.count)
amount = get.stack.maxStackSize ?: get.stack.count
amount = amount.min(get.stack.count)
if (amount <= ImpreciseFraction.ZERO)
return identity().empty()
return storageType.empty
val copy = get.stack.copy() as T
copy.count = amount
if (!simulate) {
if (amount.compareTo(get.stack.count) == 0) {
val listing = storedStacks[get.stack.partitionKey()]!!
val listing = storedStacks[get.stack.partitionKey]!!
listing.remove(get)
storedDifferentStacks--
for (listener in listeners) {
listener.removeObject(get.id())
listener.removeStack(get.id)
}
if (listing.size == 0) {
storedStacks.remove(get.stack.partitionKey())
storedStacks.remove(get.stack.partitionKey)
}
}
@ -133,7 +128,7 @@ abstract class AbstractMatteryDrive<T : IStorageStack> @JvmOverloads constructor
if (!get.stack.count.isZero) {
for (listener in listeners) {
listener.changeObject(get.id(), get.stack.count)
listener.changeStack(get.id, get.stack.count)
}
}
@ -160,7 +155,7 @@ abstract class AbstractMatteryDrive<T : IStorageStack> @JvmOverloads constructor
val serialized = serializeStack(stack)
if (serialized != null) {
serialized["id"] = longArrayOf(stack.id().mostSignificantBits, stack.id().leastSignificantBits)
serialized["id"] = longArrayOf(stack.id.mostSignificantBits, stack.id.leastSignificantBits)
list.add(serialized)
}
}
@ -191,19 +186,15 @@ abstract class AbstractMatteryDrive<T : IStorageStack> @JvmOverloads constructor
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()) { ArrayList() }.add(tuple)
storedStacks.computeIfAbsent(stack.partitionKey, Long2ObjectFunction { ArrayList() }).add(tuple)
storedByID[tuple.id] = tuple
}
}
}
}
override fun storageIdentity(): Class<T> {
return identity().identity()
}
override fun getStack(id: UUID): T {
return storedByID[id]?.stack ?: identity().empty()
return storedByID[id]?.stack ?: storageType.empty
}
override fun getStacks(): List<IStorageTuple<T>> {
@ -221,7 +212,6 @@ abstract class AbstractMatteryDrive<T : IStorageStack> @JvmOverloads constructor
return list
}
@JvmField
protected val listeners = HashSet<IStorageListener<T>>()
override fun addListener(listener: IStorageListener<T>): Boolean {

View File

@ -6,12 +6,12 @@ import net.minecraft.world.item.Item
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.Items
import net.minecraftforge.registries.RegistryManager
import ru.dbotthepony.mc.otm.core.Fraction
import ru.dbotthepony.mc.otm.OverdriveThatMatters
import ru.dbotthepony.mc.otm.core.ImpreciseFraction
import ru.dbotthepony.mc.otm.set
import ru.dbotthepony.mc.otm.storage.IStorageTuple
import ru.dbotthepony.mc.otm.storage.ItemStackWrapper
import ru.dbotthepony.mc.otm.storage.StorageObjectRegistry
import ru.dbotthepony.mc.otm.storage.StorageStackType
import java.util.*
class ItemMatteryDrive : AbstractMatteryDrive<ItemStackWrapper>, IItemMatteryDrive {
@ -20,16 +20,15 @@ class ItemMatteryDrive : AbstractMatteryDrive<ItemStackWrapper>, IItemMatteryDri
constructor(capacity: ImpreciseFraction, uuid: UUID) : super(capacity, uuid)
constructor(capacity: ImpreciseFraction) : super(capacity)
private val identity = StorageObjectRegistry.getOrError(ItemStackWrapper::class.java)
override fun identity() = identity
override val storageType: StorageStackType<ItemStackWrapper> = OverdriveThatMatters.INSTANCE.ITEM_STORAGE()
fun insertObject(item: ItemStack, simulate: Boolean): ItemStack {
fun insertStack(item: ItemStack, simulate: Boolean): ItemStack {
return insertStack(ItemStackWrapper(item), simulate).stack
}
override fun serializeStack(item: IStorageTuple<ItemStackWrapper>): CompoundTag? {
val tag = CompoundTag()
val location = item.stack().stack.item.registryName!!.toString()
val location = item.stack.stack.item.registryName!!.toString()
tag["item"] = location
tag["count"] = item.stack.stack.count
@ -57,12 +56,12 @@ class ItemMatteryDrive : AbstractMatteryDrive<ItemStackWrapper>, IItemMatteryDri
}
override fun findItems(item: Item): List<IStorageTuple<ItemStackWrapper>> {
val list = storedStacks[item]
val list = storedStacks[ItemStackWrapper.partitionKey(item)]
return if (list != null) java.util.List.copyOf(list) else emptyList()
}
override fun findItems(stack: ItemStack): List<IStorageTuple<ItemStackWrapper>> {
val list = storedStacks[stack.item] ?: return emptyList()
val list = storedStacks[ItemStackWrapper.partitionKey(stack.item)] ?: return emptyList()
var amount = 0

View File

@ -2,14 +2,15 @@ package ru.dbotthepony.mc.otm.graph.storage
import net.minecraftforge.common.util.LazyOptional
import ru.dbotthepony.mc.otm.graph.Graph6Node
import ru.dbotthepony.mc.otm.storage.IStorageIdentity
import ru.dbotthepony.mc.otm.storage.IStorage
import ru.dbotthepony.mc.otm.storage.IStorageStack
import ru.dbotthepony.mc.otm.storage.StorageStackType
import java.util.*
open class BasicStorageGraphNode : IStorageGraphNode {
private var resolver = LazyOptional.of<IStorageGraphNode> { this }
private var valid = true
protected val components = ArrayList<IStorageIdentity<*>>()
protected val components = ArrayList<IStorage<*>>()
private val node = Graph6Node<IStorageGraphNode>(this)
@ -17,25 +18,14 @@ open class BasicStorageGraphNode : IStorageGraphNode {
return node
}
override fun getComponents(): List<IStorageIdentity<*>> {
override fun fetchComponents(): List<IStorage<*>> {
return Collections.unmodifiableList(components)
}
fun <T : IStorageStack, U : IStorageIdentity<T>> computeIfAbsent(identity: Class<T>, provider: () -> U): U {
@Suppress("unchecked_cast")
fun <T : IStorageStack, U : IStorage<T>> computeIfAbsent(identity: StorageStackType<T>, provider: (StorageStackType<T>) -> U): U {
for (component in components) {
if (component.storageIdentity() == identity) {
return component as U
}
}
val factory = provider()
addStorageComponent(factory)
return factory
}
fun <T : IStorageStack, U : IStorageIdentity<T>> computeIfAbsent(identity: Class<T>, provider: (Class<T>) -> U): U {
for (component in components) {
if (component.storageIdentity() == identity) {
if (component.storageType === identity) {
return component as U
}
}
@ -45,25 +35,15 @@ open class BasicStorageGraphNode : IStorageGraphNode {
return factory
}
fun addStorageComponent(component: IStorageIdentity<*>) {
fun addStorageComponent(component: IStorage<*>) {
for (component1 in components) {
if (component === component1 || component1.storageIdentity() == component.storageIdentity()) {
if (component === component1 || component1.storageType === component.storageType) {
return
}
}
components.add(component)
getStorageGraph()?.add(component)
}
fun removeStorageComponent(component: IStorageIdentity<*>) {
for (component1 in components) {
if (component === component1 || component1.storageIdentity() == component.storageIdentity()) {
components.remove(component1)
getStorageGraph()?.remove(component)
return
}
}
storageGraph?.add(component)
}
fun invalidate() {

View File

@ -2,7 +2,7 @@ package ru.dbotthepony.mc.otm.graph.storage
import net.minecraft.MethodsReturnNonnullByDefault
import ru.dbotthepony.mc.otm.graph.Graph6Node
import ru.dbotthepony.mc.otm.storage.IStorageIdentity
import ru.dbotthepony.mc.otm.storage.IStorage
import javax.annotation.ParametersAreNonnullByDefault
@MethodsReturnNonnullByDefault
@ -13,10 +13,10 @@ interface IStorageGraphNode {
*
* If you don't know what you are going to store, you may return nothing.
*
* If something pops up, call [getStorageGraph], and then, call [StorageNetworkGraph.add].
* If something pops up, call [storageGraph], and then, call [StorageNetworkGraph.add].
*/
fun getComponents(): Collection<IStorageIdentity<*>>
fun fetchComponents(): Collection<IStorage<*>>
fun getAsStorageNode(): Graph6Node<IStorageGraphNode>
fun getStorageGraph(): StorageNetworkGraph? = getAsStorageNode().graph as StorageNetworkGraph?
val storageGraph: StorageNetworkGraph? get() = getAsStorageNode().graph as StorageNetworkGraph?
}

View File

@ -1,44 +1,51 @@
package ru.dbotthepony.mc.otm.graph.storage
import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap
import it.unimi.dsi.fastutil.objects.Object2ObjectFunction
import net.minecraft.server.level.ServerLevel
import net.minecraft.world.level.block.entity.BlockEntity
import ru.dbotthepony.mc.otm.capability.MatteryCapability
import ru.dbotthepony.mc.otm.graph.Abstract6Graph
import ru.dbotthepony.mc.otm.graph.Graph6Node
import ru.dbotthepony.mc.otm.storage.IStorageIdentity
import ru.dbotthepony.mc.otm.storage.IStorageStack
import ru.dbotthepony.mc.otm.storage.VirtualComponent
import ru.dbotthepony.mc.otm.storage.*
class StorageNetworkGraph : Abstract6Graph<IStorageGraphNode>() {
private val virtualComponents = HashMap<Class<out IStorageStack>, VirtualComponent<out IStorageStack>>()
private val virtualComponents = Object2ObjectArrayMap<StorageStackType<*>, VirtualComponent<*>>()
fun <T : IStorageStack> insertObject(type: Class<T>, obj: T, simulate: Boolean): T {
return getVirtualComponent(type).insertStack(obj, simulate)
fun <T : IStorageStack> insertStack(obj: T, simulate: Boolean): T {
return (getVirtualComponent(StorageRegistry.get(obj::class.java)) as VirtualComponent<T>).insertStack(obj, simulate)
}
/**
* Returns a [VirtualComponent] representing [type] storage
*/
fun <T : IStorageStack> getVirtualComponent(type: StorageStackType<T>): VirtualComponent<T> {
return virtualComponents.computeIfAbsent(type, Object2ObjectFunction { VirtualComponent(type) }) as VirtualComponent<T>
}
/**
* Returns a [VirtualComponent] representing [type] storage
*/
fun <T : IStorageStack> getVirtualComponent(type: Class<T>): VirtualComponent<T> {
return virtualComponents.computeIfAbsent(type) { VirtualComponent(type) } as VirtualComponent<T>
return virtualComponents.computeIfAbsent(StorageRegistry.get(type), Object2ObjectFunction { VirtualComponent(type) }) as VirtualComponent<T>
}
fun <T : IStorageStack> add(identity: IStorageIdentity<T>) {
getVirtualComponent(identity.storageIdentity()).add(identity)
fun <T : IStorageStack> add(storage: IStorage<T>) {
getVirtualComponent(storage.storageType).add(storage)
}
fun <T : IStorageStack> remove(identity: IStorageIdentity<T>) {
getVirtualComponent(identity.storageIdentity()).remove(identity)
fun <T : IStorageStack> remove(storage: IStorage<T>) {
getVirtualComponent(storage.storageType).remove(storage)
}
override fun onNodeAdded(node: Graph6Node<IStorageGraphNode>) {
for (identity in node.value.getComponents()) {
for (identity in node.value.fetchComponents()) {
add(identity)
}
}
override fun onNodeRemoved(node: Graph6Node<IStorageGraphNode>) {
for (identity in node.value.getComponents()) {
for (identity in node.value.fetchComponents()) {
remove(identity)
}
}

View File

@ -223,7 +223,7 @@ class PortableCondensationDriveItem(capacity: Int) :
if (filter.matches(event.item.item)) {
val copy = event.item.item.copy()
val remaining = (it as ItemMatteryDrive).insertObject(event.item.item, false)
val remaining = (it as ItemMatteryDrive).insertStack(event.item.item, false)
if (remaining.count == event.item.item.count) {
return@ifPresent

View File

@ -16,6 +16,7 @@ import net.minecraftforge.eventbus.api.SubscribeEvent
import net.minecraftforge.network.NetworkEvent
import net.minecraftforge.network.PacketDistributor
import org.lwjgl.glfw.GLFW
import ru.dbotthepony.mc.otm.OverdriveThatMatters
import ru.dbotthepony.mc.otm.capability.MatteryCapability
import ru.dbotthepony.mc.otm.capability.drive.IMatteryDrive
import ru.dbotthepony.mc.otm.core.ImpreciseFraction
@ -201,7 +202,7 @@ private fun getMatterValue(stack: ItemStack, level: Int): MatterTuple {
val drive = stack.getCapability(MatteryCapability.DRIVE).orNull()
if (drive != null && drive.storageIdentity() === ItemStackWrapper.javaClass) {
if (drive != null && drive.storageType === OverdriveThatMatters.INSTANCE.ITEM_STORAGE()) {
for (item in (drive as IMatteryDrive<ItemStackWrapper>).getStacks()) {
val tuple = getMatterValue(item.stack.stack, level + 1)

View File

@ -7,6 +7,7 @@ import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.ItemStack
import net.minecraftforge.energy.CapabilityEnergy
import net.minecraftforge.network.NetworkEvent
import ru.dbotthepony.mc.otm.OverdriveThatMatters
import ru.dbotthepony.mc.otm.block.entity.DriveViewerBlockEntity
import ru.dbotthepony.mc.otm.capability.MatteryCapability
import ru.dbotthepony.mc.otm.capability.drive.IMatteryDrive
@ -70,7 +71,7 @@ class DriveViewerMenu @JvmOverloads constructor(
if (!itemStack.isEmpty) {
itemStack.getCapability(MatteryCapability.DRIVE).ifPresent {
if (it.storageIdentity() == ItemStackWrapper::class.java) {
if (it.storageType === OverdriveThatMatters.INSTANCE.ITEM_STORAGE()) {
lastDrive = it as IMatteryDrive<ItemStackWrapper>
}
}
@ -123,7 +124,7 @@ class DriveViewerMenu @JvmOverloads constructor(
if (remaining.count.toInt() == item.count)
return ItemStack.EMPTY
if (remaining.isEmpty()) {
if (remaining.isEmpty) {
val copy = item.copy()
slot.set(ItemStack.EMPTY)
return copy

View File

@ -3,6 +3,7 @@ package ru.dbotthepony.mc.otm.menu
import net.minecraft.world.entity.player.Inventory
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.ItemStack
import ru.dbotthepony.mc.otm.OverdriveThatMatters
import ru.dbotthepony.mc.otm.block.entity.ItemMonitorBlockEntity
import ru.dbotthepony.mc.otm.capability.MatteryCapability
import ru.dbotthepony.mc.otm.menu.data.INetworkedItemViewSupplier
@ -28,7 +29,7 @@ class ItemMonitorMenu @JvmOverloads constructor(
init {
if (tile != null) {
subscribed = tile.cell.getStorageGraph()!!.getVirtualComponent(ItemStackWrapper::class.java)
subscribed = tile.cell.storageGraph!!.getVirtualComponent(OverdriveThatMatters.INSTANCE.ITEM_STORAGE())
local = PoweredVirtualComponent(subscribed, tile.getCapability(MatteryCapability.ENERGY).resolve().get())
view.setComponent(local)
} else {

View File

@ -77,8 +77,8 @@ open class NetworkedItemView(val ply: Player, val menu: MatteryMenu, val remote:
return state.values.size
}
override fun addObject(stack: ItemStackWrapper, id: UUID, provider: IStorageView<ItemStackWrapper>) = addObject(stack.stack, id)
override fun changeObject(id: UUID, newCount: ImpreciseFraction) = changeObject(id, newCount.toInt())
override fun addStack(stack: ItemStackWrapper, id: UUID, provider: IStorageView<ItemStackWrapper>) = addObject(stack.stack, id)
override fun changeStack(id: UUID, newCount: ImpreciseFraction) = changeObject(id, newCount.toInt())
protected fun network(fn: () -> Any) {
if (!remote) {
@ -86,7 +86,7 @@ open class NetworkedItemView(val ply: Player, val menu: MatteryMenu, val remote:
}
}
override fun removeObject(id: UUID) {
override fun removeStack(id: UUID) {
val get = upstream_state[id] ?: throw IllegalStateException("Unknown ItemStack with upstream id $id!")
upstream_state.remove(id)
state.remove(get.id)
@ -162,7 +162,7 @@ open class NetworkedItemView(val ply: Player, val menu: MatteryMenu, val remote:
val extracted = provider.extractStack(state.upstreamId!!, amount, true)
if (!extracted.isEmpty()) {
if (!extracted.isEmpty) {
val (_, remaining) = menu.quickMoveToInventory(extracted.stack, false)
if (remaining.count != extracted.stack.count) {
@ -189,7 +189,7 @@ open class NetworkedItemView(val ply: Player, val menu: MatteryMenu, val remote:
val copy = menu.carried.copy()
copy.count = 1
if (provider.insertStack(ItemStackWrapper(copy), false).isEmpty()) {
if (provider.insertStack(ItemStackWrapper(copy), false).isEmpty) {
menu.carried.shrink(1)
MatteryNetworking.send(ply as ServerPlayer, SetCarriedPacket(menu.carried))
menu.setRemoteCarried(menu.carried.copy())

View File

@ -2,46 +2,53 @@ package ru.dbotthepony.mc.otm.storage
import ru.dbotthepony.mc.otm.core.ImpreciseFraction
import java.util.*
import net.minecraftforge.registries.ForgeRegistry
interface IStorageStack {
fun copy(): IStorageStack
var count: ImpreciseFraction
fun isEmpty(): Boolean
val isEmpty: Boolean
/**
* @return max stack size for this stack object
* [Optional.empty()] if unlimited (default)
* @return max stack size for this stack object,
* null if unlimited (default)
*/
fun getMaxStackSize(): Optional<ImpreciseFraction> = Optional.empty()
val maxStackSize: ImpreciseFraction? get() = null
/**
* Returns Identity utilized to partition view table (if it has any).
* Defaults to no partitioning; meaning performance will degrade much quicker than it should.
* Good object is an object that will influence on [sameItem] return result, making it return true.
* It is strictly considered that if !this.itemIdentity().equals(other.itemIdentity);
* then this.sameItem(other) will never return true.
* 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.
*
* If implemented, and storage is also partitioned properly, then performance will be flat equally to view table's performance.
* A good example of key is [ForgeRegistry.getID].
*
* Example: ItemStack#item
* Collisions are solved as in open hash map, with linear scan for required item.
*/
fun partitionKey(): Any {
return Any::class.java
}
val partitionKey: Long
/**
* [other] other object to compare; if not the same of this object type just return false
* Returns boolean representing whenever internal state of [this] equals to [other];
* this include tags, mapping IDs, capabilities, **excluding** amount;
* behavior is pretty much the same as ItemStack.isSameItemSameTags
* [other] is the stack to compare.
*
* If not the same of this object type just return false
* Returns boolean representing whenever internal state of *this* equals to [other];
*
* This includes (nbt) tags, mapping IDs, capabilities, **excluding** amount;
*
* Example of this behavior is demonstrated by ItemStack.isSameItemSameTags.
*/
fun sameItem(other: IStorageStack): Boolean
/**
* Increase [count] by [amount]
*/
fun grow(amount: ImpreciseFraction) {
count += amount
}
/**
* Decrease [count] by [amount]
*/
fun shrink(amount: ImpreciseFraction) {
count -= amount
}
@ -50,11 +57,14 @@ interface IStorageStack {
/**
* Storage system root, along IStorageStack interface
*/
interface IStorageIdentity<T : IStorageStack> {
fun storageIdentity(): Class<T>
interface IStorage<T : IStorageStack> {
/**
* @return Identity of this virtual component
*/
val storageType: StorageStackType<T>
}
interface IStorageTrigger<T : IStorageStack> : IStorageIdentity<T> {
interface IStorageTrigger<T : IStorageStack> : IStorage<T> {
/**
* [listener] is [IStorageListener] which want to subscribe to our events
*/
@ -66,19 +76,23 @@ interface IStorageTrigger<T : IStorageStack> : IStorageIdentity<T> {
fun removeListener(listener: IStorageListener<T>): Boolean
}
interface IStorageConsumer<T : IStorageStack> : IStorageIdentity<T> {
interface IStorageConsumer<T : IStorageStack> : IStorage<T> {
/**
* Inserts an item into system.
* @return leftover, might equal to [stack] if no items were inserted
*/
fun insertStack(stack: T, simulate: Boolean): T
}
interface IStorageView<T : IStorageStack> : IStorageTrigger<T> {
/**
* @param id identifier of object
* @param id identifier of stack
* @return stored object (not a copy). Do not edit it.
*/
fun getStack(id: UUID): T
/**
* @param id identifier of object to extract
* @param id identifier of stack to extract
* @param amount amount of units to extract
* @param simulate whenever to simulate the action or not
* @return copy of object, with amount of units actually extracted
@ -89,9 +103,9 @@ interface IStorageView<T : IStorageStack> : IStorageTrigger<T> {
/**
* Designed for views, for extraction with less computation overhead caused by
* copying object extracted
* copying stack extracted
*
* @param id identifier of object to extract
* @param id identifier of stack to extract
* @param amount desired amount to extract
* @param simulate whenever to simulate the action or not
* @return amount extracted
@ -108,7 +122,7 @@ interface IStorageView<T : IStorageStack> : IStorageTrigger<T> {
fun addListenerAuto(listener: IStorageListener<T>): Boolean {
if (addListener(listener)) {
for (stack in getStacks()) {
listener.addObject(stack.stack(), stack.id(), this)
listener.addStack(stack.stack, stack.id, this)
}
return true
@ -120,7 +134,7 @@ interface IStorageView<T : IStorageStack> : IStorageTrigger<T> {
fun removeListenerAuto(listener: IStorageListener<T>): Boolean {
if (removeListener(listener)) {
for (stack in getStacks()) {
listener.removeObject(stack.id())
listener.removeStack(stack.id)
}
return true
@ -134,38 +148,23 @@ interface IStorageListener<T : IStorageStack> {
/**
* Fired on whenever an object is added (to listener) we subscribed to
*/
fun addObject(stack: T, id: UUID, provider: IStorageView<T>)
fun addStack(stack: T, id: UUID, provider: IStorageView<T>)
/**
* Fired on whenever an object is changes on listener we subscribed to
*/
fun changeObject(id: UUID, newCount: ImpreciseFraction)
fun changeStack(id: UUID, newCount: ImpreciseFraction)
/**
* Fired on whenever an object is removed from listener we subscribed to
*/
fun removeObject(id: UUID)
fun removeStack(id: UUID)
}
interface IStorageTuple<T : IStorageStack> {
val id: UUID
get() = id()
val stack: T
get() = stack()
fun id(): UUID
fun stack(): T
}
data class StorageTuple<T : IStorageStack>(override val id: UUID, override val stack: T) : IStorageTuple<T> {
override fun id(): UUID {
return id
}
override fun stack(): T {
return stack
}
}
class StorageTuple<T : IStorageStack>(override val id: UUID, override val stack: T) : IStorageTuple<T>
interface IStorageComponent<T : IStorageStack> : IStorageView<T>, IStorageConsumer<T>

View File

@ -1,39 +1,37 @@
package ru.dbotthepony.mc.otm.storage
import net.minecraft.world.item.Item
import net.minecraft.world.item.ItemStack
import ru.dbotthepony.mc.otm.core.Fraction
import net.minecraftforge.registries.ForgeRegistries
import net.minecraftforge.registries.ForgeRegistry
import ru.dbotthepony.mc.otm.core.ImpreciseFraction
import java.util.*
@JvmRecord
data class ItemStackWrapper(val stack: ItemStack) : IStorageStack {
class ItemStackWrapper(val stack: ItemStack) : IStorageStack {
operator fun component1() = stack
override fun copy(): IStorageStack {
return ItemStackWrapper(stack.copy())
}
fun setCount(value: Int) {
stack.count = Math.max(value, 0)
stack.count = value.coerceAtLeast(0)
}
override var count: ImpreciseFraction
get() = ImpreciseFraction(stack.count)
set(value) = setCount(value.toInt())
fun getCountInt() = stack.count
override val maxStackSize get() = ImpreciseFraction(stack.maxStackSize)
override fun getMaxStackSize(): Optional<ImpreciseFraction> {
return Optional.of(ImpreciseFraction(stack.maxStackSize))
}
override fun isEmpty(): Boolean = stack.isEmpty
override fun partitionKey(): Any = stack.item
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(stack, other.stack)
return false
}
@ -41,5 +39,7 @@ data class ItemStackWrapper(val stack: ItemStack) : IStorageStack {
companion object {
@JvmField
val EMPTY = ItemStackWrapper(ItemStack.EMPTY)
fun partitionKey(item: Item) = (ForgeRegistries.ITEMS as ForgeRegistry<Item>?)?.getID(item)?.toLong() ?: System.identityHashCode(item).toLong()
}
}

View File

@ -0,0 +1,50 @@
package ru.dbotthepony.mc.otm.storage
import ru.dbotthepony.mc.otm.core.ImpreciseFraction
import java.util.IdentityHashMap
open class StorageStackType<T : IStorageStack>(
val identity: Class<T>,
open val empty: T,
/**
* Speculated energy required per operation on stack with size of 1
*/
open val energyPerOperation: ImpreciseFraction
) {
open fun energyPerOperation(stack: T): ImpreciseFraction {
return stack.count * energyPerOperation
}
open fun energyPerStorage(stack: T) = energyPerOperation(stack)
open fun energyPerExtraction(stack: T) = energyPerOperation(stack)
open fun energyForUpkeep(stack: T) = ImpreciseFraction.ZERO
}
@Suppress("unchecked_cast")
object StorageRegistry {
private val REGISTRY = IdentityHashMap<Class<*>, StorageStackType<*>>()
@JvmStatic
fun register(type: StorageStackType<*>): StorageStackType<*> {
check(!REGISTRY.containsKey(type.identity)) { "Already have storage stack type for ${type.identity}" }
REGISTRY[type.identity] = type
return type
}
@JvmStatic
fun <T : IStorageStack> register(identity: Class<T>, empty: T, energyPerOperation: ImpreciseFraction): StorageStackType<T> {
return register(StorageStackType(identity, empty, energyPerOperation)) as StorageStackType<T>
}
@JvmStatic
fun <T : IStorageStack> get(identity: Class<T>): StorageStackType<T> {
val get = REGISTRY[identity] ?: throw NoSuchElementException("Registry does not contain $identity")
return get as StorageStackType<T>
}
@JvmStatic
fun <T : IStorageStack> getOrNull(identity: Class<T>): StorageStackType<T>? {
return REGISTRY[identity] as StorageStackType<T>?
}
}

View File

@ -1,7 +1,9 @@
package ru.dbotthepony.mc.otm.storage
import it.unimi.dsi.fastutil.longs.Long2ObjectAVLTreeMap
import it.unimi.dsi.fastutil.longs.Long2ObjectFunction
import it.unimi.dsi.fastutil.objects.Object2ObjectAVLTreeMap
import ru.dbotthepony.mc.otm.capability.IMatteryEnergyStorage
import ru.dbotthepony.mc.otm.core.Fraction
import ru.dbotthepony.mc.otm.core.ImpreciseFraction
import java.util.*
@ -23,45 +25,27 @@ class RemoteTuple<T : IStorageStack>(val obj: T, val remote_id: UUID, val provid
}
}
class LocalTuple<T : IStorageStack>(override val stack: T, override val id: UUID, val tuples: ArrayList<RemoteTuple<T>>) : IStorageTuple<T> {
override fun id(): UUID {
return id
}
class LocalTuple<T : IStorageStack>(override val stack: T, override val id: UUID, val tuples: ArrayList<RemoteTuple<T>>) : IStorageTuple<T>
override fun stack(): T {
return stack
}
}
open class VirtualComponent<T : IStorageStack>(type: StorageStackType<T>) : IStorageComponent<T>, IStorageListener<T> {
constructor(type: Class<T>) : this(StorageRegistry.get(type))
open class VirtualComponent<T : IStorageStack>(identity: Class<T>) : IStorageComponent<T>, IStorageListener<T> {
@JvmField
protected val identity: StorageObjectTuple<T>
final override fun storageIdentity() = identity.identity()
init {
this.identity = StorageObjectRegistry.getOrError(identity)
}
override val storageType: StorageStackType<T> = type
// удаленный UUID -> Кортеж
@JvmField
protected val remoteByUUID = HashMap<UUID, RemoteTuple<T>>()
protected val remoteByUUID = Object2ObjectAVLTreeMap<UUID, RemoteTuple<T>>()
// локальный UUID -> Локальный кортеж
@JvmField
protected val localByUUID = HashMap<UUID, LocalTuple<T>>()
protected val localByUUID = Object2ObjectAVLTreeMap<UUID, LocalTuple<T>>()
// Хеш ключ -> Список Локальных кортежей
@JvmField
protected val partitions = HashMap<Any, ArrayList<LocalTuple<T>>>()
// ключ -> Список Локальных кортежей
protected val partitions = Long2ObjectAVLTreeMap<ArrayList<LocalTuple<T>>>()
// ArrayList для скорости работы
@JvmField
protected val listeners = ArrayList<IStorageListener<T>>()
@JvmField
protected val consumers = ArrayList<IStorageConsumer<T>>()
open fun add(identity: IStorageIdentity<T>) {
open fun add(identity: IStorage<T>) {
if (identity is IStorageView<T>) {
identity.addListenerAuto(this)
} else if (identity is IStorageTrigger<T>) {
@ -69,11 +53,11 @@ open class VirtualComponent<T : IStorageStack>(identity: Class<T>) : IStorageCom
}
if (identity is IStorageConsumer<T> && !consumers.contains(identity)) {
consumers.add(identity);
consumers.add(identity)
}
}
open fun remove(identity: IStorageIdentity<T>) {
open fun remove(identity: IStorage<T>) {
if (identity is IStorageView<T>) {
identity.removeListenerAuto(this)
} else if (identity is IStorageTrigger<T>) {
@ -99,7 +83,7 @@ open class VirtualComponent<T : IStorageStack>(identity: Class<T>) : IStorageCom
}
override fun getStack(id: UUID): T {
return localByUUID[id]?.stack ?: identity.empty
return localByUUID[id]?.stack ?: this.storageType.empty
}
override fun getStacks(): List<IStorageTuple<T>> {
@ -110,9 +94,10 @@ open class VirtualComponent<T : IStorageStack>(identity: Class<T>) : IStorageCom
return output
}
override fun addObject(stack: T, id: UUID, provider: IStorageView<T>) {
@Suppress("unchecked_cast")
override fun addStack(stack: T, id: UUID, provider: IStorageView<T>) {
check(!remoteByUUID.containsKey(id)) { "Already tracking tuple with id $id" }
val items = partitions.computeIfAbsent(stack.partitionKey()) { ArrayList() }
val items = partitions.computeIfAbsent(stack.partitionKey, Long2ObjectFunction { ArrayList() })
var local: LocalTuple<T>? = null
for (item in items) {
@ -137,16 +122,16 @@ open class VirtualComponent<T : IStorageStack>(identity: Class<T>) : IStorageCom
if (added) {
for (listener in listeners) {
listener.addObject(local.stack, local.id, this)
listener.addStack(local.stack, local.id, this)
}
} else {
for (listener in listeners) {
listener.changeObject(local.id, local.stack.count)
listener.changeStack(local.id, local.stack.count)
}
}
}
override fun changeObject(id: UUID, newCount: ImpreciseFraction) {
override fun changeStack(id: UUID, newCount: ImpreciseFraction) {
assert(newCount > ImpreciseFraction.ZERO)
val tuple = remoteByUUID[id] ?: throw IllegalStateException("No such tuple with id $id")
@ -155,13 +140,13 @@ open class VirtualComponent<T : IStorageStack>(identity: Class<T>) : IStorageCom
tuple.local.stack.grow(diff)
for (listener in listeners) {
listener.changeObject(tuple.local.id, tuple.local.stack.count)
listener.changeStack(tuple.local.id, tuple.local.stack.count)
}
}
override fun removeObject(id: UUID) {
override fun removeStack(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.partitionKey
tuple.local.stack.shrink(tuple.obj.count)
tuple.local.tuples.remove(tuple)
@ -177,7 +162,7 @@ open class VirtualComponent<T : IStorageStack>(identity: Class<T>) : IStorageCom
partitions[key]!!.remove(tuple.local)
for (listener in listeners) {
listener.removeObject(tuple.local.id)
listener.removeStack(tuple.local.id)
}
}
}
@ -188,7 +173,7 @@ open class VirtualComponent<T : IStorageStack>(identity: Class<T>) : IStorageCom
for (consumer in consumers) {
leftover = consumer.insertStack(leftover, simulate)
if (leftover.isEmpty()) {
if (leftover.isEmpty) {
return leftover
}
}
@ -196,18 +181,20 @@ open class VirtualComponent<T : IStorageStack>(identity: Class<T>) : IStorageCom
return leftover
}
@Suppress("unchecked_cast")
override fun extractStack(id: UUID, amount: ImpreciseFraction, simulate: Boolean): T {
if (amount.isZero)
return identity.empty
return this.storageType.empty
@Suppress("name_shadowing")
var amount = amount
val tuple: LocalTuple<T>? = localByUUID[id]
if (tuple == null || amount.isZero)
return identity.empty
return this.storageType.empty
if (amount <= ImpreciseFraction.MINUS_ONE)
amount = tuple.stack.getMaxStackSize().orElse(tuple.stack.count)
amount = tuple.stack.maxStackSize ?: tuple.stack.count
val toExtract = tuple.stack.count.min(amount)
var extracted = ImpreciseFraction.ZERO
@ -225,19 +212,21 @@ open class VirtualComponent<T : IStorageStack>(identity: Class<T>) : IStorageCom
return copy
}
return identity.empty()
return this.storageType.empty
}
}
open class PoweredVirtualComponent<T : IStorageStack>(identity: Class<T>, @JvmField val energyProvider: () -> IMatteryEnergyStorage) : VirtualComponent<T>(identity) {
constructor(identity: Class<T>, energyStorage: IMatteryEnergyStorage) : this(identity, {energyStorage})
constructor(other: VirtualComponent<T>, energyStorage: IMatteryEnergyStorage) : this(other.storageIdentity(), energyStorage) {
open class PoweredVirtualComponent<T : IStorageStack>(type: StorageStackType<T>, val energyProvider: () -> IMatteryEnergyStorage) : VirtualComponent<T>(type) {
constructor(type: Class<T>, energyStorage: IMatteryEnergyStorage) : this(StorageRegistry.get(type), {energyStorage})
constructor(type: StorageStackType<T>, energyStorage: IMatteryEnergyStorage) : this(type, {energyStorage})
constructor(other: VirtualComponent<T>, energyStorage: IMatteryEnergyStorage) : this(other.storageType.identity, energyStorage) {
add(other)
}
@Suppress("unchecked_cast")
override fun insertStack(stack: T, simulate: Boolean): T {
val required = identity.energyPerOperation * stack.count
val energy = energyProvider()
val required = storageType.energyPerOperation * stack.count
val energy = energyProvider.invoke()
val extracted = energy.extractEnergyInner(required, true)
if (extracted.isZero) {
@ -247,7 +236,7 @@ open class PoweredVirtualComponent<T : IStorageStack>(identity: Class<T>, @JvmFi
if (extracted == required) {
val leftover = super.insertStack(stack, simulate)
if (leftover.isEmpty()) {
if (leftover.isEmpty) {
if (!simulate) {
energy.extractEnergyInner(required, false)
}
@ -256,24 +245,25 @@ open class PoweredVirtualComponent<T : IStorageStack>(identity: Class<T>, @JvmFi
}
if (!simulate) {
val requiredNew = identity.energyPerOperation * stack.count - leftover.count
val requiredNew = storageType.energyPerOperation * stack.count - leftover.count
energy.extractEnergyInner(requiredNew, false)
}
return leftover
}
@Suppress("name_shadowing")
val stack = stack.copy() as T
val oldCount = stack.count
stack.count = extracted / identity.energyPerOperation
stack.count = extracted / storageType.energyPerOperation
val diff = oldCount - stack.count
val newRequired = identity.energyPerOperation * stack.count
val newRequired = storageType.energyPerOperation * stack.count
val newExtracted = energy.extractEnergyInner(newRequired, true)
if (newExtracted == newRequired) {
val leftover = super.insertStack(stack, simulate)
if (leftover.isEmpty()) {
if (leftover.isEmpty) {
if (!simulate) {
energy.extractEnergyInner(newRequired, false)
}
@ -283,7 +273,7 @@ open class PoweredVirtualComponent<T : IStorageStack>(identity: Class<T>, @JvmFi
}
if (!simulate) {
val requiredNew = (stack.count - leftover.count) * identity.energyPerOperation
val requiredNew = (stack.count - leftover.count) * storageType.energyPerOperation
energy.extractEnergyInner(requiredNew, false)
}
@ -295,18 +285,18 @@ open class PoweredVirtualComponent<T : IStorageStack>(identity: Class<T>, @JvmFi
}
override fun extractStack(id: UUID, amount: ImpreciseFraction, simulate: Boolean): T {
val required = identity.energyPerOperation * amount
val energy = energyProvider()
val required = storageType.energyPerOperation * amount
val energy = energyProvider.invoke()
val extracted = energy.extractEnergyInner(required, true)
if (extracted.isZero) {
return identity.empty
return storageType.empty
}
if (extracted == required) {
val extractedStack = super.extractStack(id, amount, simulate)
if (extractedStack.isEmpty()) {
if (extractedStack.isEmpty) {
return extractedStack
}
@ -314,22 +304,23 @@ open class PoweredVirtualComponent<T : IStorageStack>(identity: Class<T>, @JvmFi
if (extractedStack.count == amount) {
energy.extractEnergyInner(required, false)
} else {
energy.extractEnergyInner(extractedStack.count * identity.energyPerOperation, false)
energy.extractEnergyInner(extractedStack.count * storageType.energyPerOperation, false)
}
}
return extractedStack
}
val amount = required / identity.energyPerOperation
@Suppress("name_shadowing")
val amount = required / storageType.energyPerOperation
val extractedStack = super.extractStack(id, amount, simulate)
if (extractedStack.isEmpty()) {
if (extractedStack.isEmpty) {
return extractedStack
}
if (!simulate) {
energy.extractEnergyInner(extractedStack.count * identity.energyPerOperation, false)
energy.extractEnergyInner(extractedStack.count * storageType.energyPerOperation, false)
}
return extractedStack