diff --git a/src/main/scala/li/cil/oc/integration/appeng/DriverExportBus.scala b/src/main/scala/li/cil/oc/integration/appeng/DriverExportBus.scala index b91fe8d54f..b3b72fdd37 100644 --- a/src/main/scala/li/cil/oc/integration/appeng/DriverExportBus.scala +++ b/src/main/scala/li/cil/oc/integration/appeng/DriverExportBus.scala @@ -1,11 +1,15 @@ package li.cil.oc.integration.appeng -import appeng.api.AEApi -import appeng.api.config.{Actionable, FuzzyMode, Settings, Upgrades} -import appeng.api.networking.security.MachineSource +import appeng.api.config._ import appeng.api.parts.IPartHost +import appeng.api.storage.StorageName import appeng.api.storage.data.IAEItemStack -import appeng.parts.automation.PartExportBus +import appeng.core.settings.TickRates +import appeng.helpers.MultiCraftingTracker +import appeng.me.GridAccessException +import appeng.me.cache.NetworkMonitor +import appeng.parts.automation.{PartBaseExportBus, PartExportBus} +import appeng.util.{IterationCounter, Platform} import li.cil.oc.api.driver import li.cil.oc.api.driver.{EnvironmentProvider, NamedBlock} import li.cil.oc.api.machine.{Arguments, Callback, Context} @@ -13,12 +17,12 @@ import li.cil.oc.integration.ManagedTileEntityEnvironment import li.cil.oc.integration.appeng.internal.PartItemBusBase import li.cil.oc.util.ExtendedArguments._ import li.cil.oc.util.ResultWrapper._ -import li.cil.oc.util.{BlockPosition, InventoryUtils} +import li.cil.oc.util.{BlockPosition, InventoryUtils, ReflectionUtils} import net.minecraft.item.ItemStack import net.minecraft.world.World import net.minecraftforge.common.util.ForgeDirection -import scala.collection.convert.WrapAsScala._ +import java.util import scala.reflect.ClassTag object DriverExportBus extends driver.SidedBlock { @@ -52,54 +56,109 @@ object DriverExportBus extends driver.SidedBlock { @Callback(doc = "function(side:number, filter: String):boolean -- Set the ore filter of the export bus pointing in the specified direction.") def setExportOreFilter(context: Context, args: Arguments): Array[AnyRef] = this.setPartOreFilter(context, args) - @Callback(doc = "function(side:number, slot:number):boolean -- Make the export bus facing the specified direction perform a single export operation into the specified slot.") + private lazy val craftingTrackerGetterHandle = ReflectionUtils.getFieldGetterHandle(classOf[PartBaseExportBus[_]], "craftingTracker", allowSuper = false) + + @Callback(doc = "function(side:number, slot:number):boolean, number -- Make the export bus facing the specified direction perform a single export operation into the specified slot.") def exportIntoSlot(context: Context, args: Arguments): Array[AnyRef] = { val side = args.checkSideAny(0) val export = getPart(side) InventoryUtils.inventoryAt(BlockPosition(host.getLocation).offset(side)) match { case Some(inventory) => - val targetSlot = args.checkSlot(inventory, 1) - val config = export.getInventoryByName("config") - val itemStorage = export.getProxy.getStorage.getItemInventory - var count = export.calculateAmountToSend() - // We need reflection here to avoid compiling against the return and - // argument type, which has changed in rv2-beta-20 or so. - val fuzzyMode = (try export.getConfigManager.getClass.getMethod("getSetting", classOf[Enum[_]]) catch { - case _: NoSuchMethodException => export.getConfigManager.getClass.getMethod("getSetting", classOf[Settings]) - }).invoke(export.getConfigManager, Settings.FUZZY_MODE).asInstanceOf[FuzzyMode] - val source = new MachineSource(export) - var didSomething = false - for (slot <- 0 until config.getSizeInventory if count > 0) { - val filter = AEApi.instance.storage.createItemStack(config.getStackInSlot(slot)) - val stacks = - if (export.getInstalledUpgrades(Upgrades.FUZZY) > 0) - itemStorage.getStorageList.findFuzzy(filter, fuzzyMode).toSeq - else - Seq(itemStorage.getStorageList.findPrecise(filter)) - for (ais <- stacks.filter(_ != null).map(_.copy()) if count > 0) { - val is = ais.getItemStack + try { + val targetSlot = args.checkSlot(inventory, 1) + val config = export.getAEInventoryByName(StorageName.CONFIG) + val gridInv = getMonitor[IAEItemStack](export) + var count = export.calculateAmountToSend() + val origin = count + val mySrc = getMySrc(export) + var didSomething = false + val energy = export.getProxy.getEnergy + val oppositeSide = Option(side.getOpposite) + + def pushItemToTargetSlot(stack: IAEItemStack, simulate: Boolean = false): Boolean = { + val is = stack.getItemStack is.stackSize = count - if (InventoryUtils.insertIntoInventorySlot(is, inventory, Option(side.getOpposite), targetSlot, count, simulate = true)) { + if (!InventoryUtils.insertIntoInventorySlot(is, inventory, oppositeSide, targetSlot, simulate = true)) + false + else if (simulate) + true + else { + val ais = stack.copy() ais.setStackSize(count - is.stackSize) - val eais = AEApi.instance.storage.poweredExtraction(export.getProxy.getEnergy, itemStorage, ais, source) + val eais = Platform.poweredExtraction(energy, gridInv, ais, mySrc) if (eais != null) { val eis = eais.getItemStack count -= eis.stackSize didSomething = true - InventoryUtils.insertIntoInventorySlot(eis, inventory, Option(side.getOpposite), targetSlot) - if (eis.stackSize > 0) { - eais.setStackSize(eis.stackSize) - itemStorage.injectItems(ais, Actionable.MODULATE, source) + InventoryUtils.insertIntoInventorySlot(eis, inventory, oppositeSide, targetSlot) + } + true + } + } + + if (export.getInstalledUpgrades(Upgrades.ORE_FILTER) == 0) { + val supportFz = supportFuzzy(export) && export.getInstalledUpgrades(Upgrades.FUZZY) > 0 + val fuzzyMode = export.getConfigManager.getSetting(Settings.FUZZY_MODE).asInstanceOf[FuzzyMode] + val cg = export.getProxy.getCrafting + val craftOnly = export.getConfigManager.getSetting(Settings.CRAFT_ONLY) == YesNo.YES; + val isCraftingEnabled = export.getInstalledUpgrades(Upgrades.CRAFTING) > 0; + val craftingTracker = craftingTrackerGetterHandle.invoke(export).asInstanceOf[MultiCraftingTracker] + var slot = 0 + + def tryCrafting(slot: Int, filter: IAEItemStack): Boolean = { + craftingTracker.handleCrafting(slot, count, filter, export.getTile.getWorldObj, export.getProxy.getGrid, cg, mySrc) + } + + while (count > 0 && slot < getSlotSize(export)) { + val filter = config.getAEStackInSlot(slot).asInstanceOf[IAEItemStack] + if (filter != null) { + if (craftOnly) { + if (isCraftingEnabled && pushItemToTargetSlot(filter, simulate = true)) { + didSomething = tryCrafting(slot, filter) || didSomething + } + } + else { + val before = count + if (supportFz) { + gridInv match { + case monitor: NetworkMonitor[IAEItemStack] => + val it = monitor.getHandler.getSortedFuzzyItems(new util.ArrayList, filter, fuzzyMode, IterationCounter.fetchNewId).iterator() + while (count > 0 && it.hasNext) { + val ais = it.next() + if (ais != null) pushItemToTargetSlot(ais) + } + case _ => + } + } + else pushItemToTargetSlot(filter) + + if (count == before && isCraftingEnabled) { + didSomething = tryCrafting(slot, filter) || didSomething + } } } + slot += 1 } } + else if (supportOreDict(export)) { + val filterPredicate = getOreFilterPredicate(export) + if (filterPredicate != null) { + val it = gridInv.getStorageList.iterator() + while (count > 0 && it.hasNext) { + val ais = it.next() + if (ais != null && filterPredicate.test(ais)) pushItemToTargetSlot(ais) + } + } + } + if (didSomething) { + context.pause((TickRates.ExportBus.getMin - 1) * 0.05) + } + result(didSomething, origin - count) } - if (didSomething) { - context.pause(0.25) + catch { + case _: GridAccessException => result(null, "grid access exception") } - result(didSomething) - case _ => result(Unit, "no inventory") + case _ => result(null, "no inventory") } } } diff --git a/src/main/scala/li/cil/oc/integration/appeng/DriverImportBus.scala b/src/main/scala/li/cil/oc/integration/appeng/DriverImportBus.scala index 58ae8bd34c..ec785cb2ba 100644 --- a/src/main/scala/li/cil/oc/integration/appeng/DriverImportBus.scala +++ b/src/main/scala/li/cil/oc/integration/appeng/DriverImportBus.scala @@ -1,13 +1,22 @@ package li.cil.oc.integration.appeng +import appeng.api.config._ import appeng.api.parts.IPartHost +import appeng.api.storage.StorageName import appeng.api.storage.data.IAEItemStack +import appeng.core.settings.TickRates +import appeng.me.GridAccessException import appeng.parts.automation.PartImportBus +import appeng.util.Platform +import appeng.util.item.AEItemStack import li.cil.oc.api.driver import li.cil.oc.api.driver.{EnvironmentProvider, NamedBlock} import li.cil.oc.api.machine.{Arguments, Callback, Context} import li.cil.oc.integration.ManagedTileEntityEnvironment import li.cil.oc.integration.appeng.internal.PartItemBusBase +import li.cil.oc.util.ExtendedArguments.extendedArguments +import li.cil.oc.util.ResultWrapper._ +import li.cil.oc.util.{BlockPosition, InventoryUtils} import net.minecraft.item.ItemStack import net.minecraft.world.World import net.minecraftforge.common.util.ForgeDirection @@ -44,6 +53,76 @@ object DriverImportBus extends driver.SidedBlock { @Callback(doc = "function(side:number, filter: String):boolean -- Set the ore filter of the import bus pointing in the specified direction.") def setImportOreFilter(context: Context, args: Arguments): Array[AnyRef] = this.setPartOreFilter(context, args) + + @Callback(doc = "function(side:number, slot:number):boolean, number -- Make the import bus facing the specified direction perform a single import operation from the specified slot.") + def importFromSlot(context: Context, args: Arguments): Array[AnyRef] = { + val side = args.checkSideAny(0) + val part = getPart(side) + InventoryUtils.inventoryAt(BlockPosition(host.getLocation).offset(side)) match { + case Some(inventory) => + try { + val targetSlot = args.checkSlot(inventory, 1) + val stack = inventory.getStackInSlot(targetSlot) + if (stack == null || stack.stackSize == 0) { + result(false) + } + else { + var isMatch = false + var configured = false + val gridInv = getMonitor[IAEItemStack](part) + if (part.getInstalledUpgrades(Upgrades.ORE_FILTER) == 0) { + val supportFz = supportFuzzy(part) && part.getInstalledUpgrades(Upgrades.FUZZY) > 0 + val fuzzyMode = part.getConfigManager.getSetting(Settings.FUZZY_MODE).asInstanceOf[FuzzyMode] + val config = part.getAEInventoryByName(StorageName.CONFIG) + var slot = 0 + while (!isMatch && slot < getSlotSize(part)) { + val filter = config.getAEStackInSlot(slot).asInstanceOf[IAEItemStack] + if (filter != null) { + configured = true + if (supportFz && Platform.isSameItemFuzzy(stack, filter.getItemStack, fuzzyMode)) + isMatch = true + else if (!supportFz && Platform.isSameItemPrecise(stack, filter.getItemStack)) + isMatch = true + } + slot += 1 + } + if (!configured) isMatch = true + } else if (supportOreDict(part)) { + val filterPredicate = getOreFilterPredicate(part) + if (filterPredicate != null) { + isMatch = filterPredicate.test(AEItemStack.create(stack)) + } + } + if (!isMatch) result(false) + else { + var didSomething = false + var insert = 0 + val energy = part.getProxy.getEnergy + InventoryUtils.extractFromInventorySlot(extracted => { + if (extracted != null) { + val before = extracted.stackSize + val source = getMySrc(part) + val failed = Platform.poweredInsert(energy, gridInv, AEItemStack.create(extracted), source) + extracted.stackSize = if (failed == null) 0 else failed.getStackSize.toInt + if (before != extracted.stackSize) { + insert = before - extracted.stackSize + didSomething = true + } + } + }, inventory, side.getOpposite, targetSlot, part.calculateAmountToSend()) + if (didSomething) { + context.pause((TickRates.ImportBus.getMin - 1) * 0.05) + } + result(didSomething, insert) + } + } + } + catch { + case _: GridAccessException => result(null, "grid access exception") + } + case _ => result(null, "no inventory") + } + } } object Provider extends EnvironmentProvider { diff --git a/src/main/scala/li/cil/oc/integration/appeng/internal/PartEnvironmentBase.scala b/src/main/scala/li/cil/oc/integration/appeng/internal/PartEnvironmentBase.scala index 55a54d5a0b..81036be6a6 100644 --- a/src/main/scala/li/cil/oc/integration/appeng/internal/PartEnvironmentBase.scala +++ b/src/main/scala/li/cil/oc/integration/appeng/internal/PartEnvironmentBase.scala @@ -1,22 +1,26 @@ package li.cil.oc.integration.appeng.internal import appeng.api.config.Upgrades +import appeng.api.networking.security.MachineSource import appeng.api.parts.{IPart, IPartHost} -import appeng.api.storage.StorageName import appeng.api.storage.data.{IAEItemStack, IAEStack} +import appeng.api.storage.{IMEMonitor, StorageName} import appeng.helpers.IOreFilterable import appeng.parts.automation.PartSharedItemBus import appeng.parts.misc.PartStorageBus import appeng.tile.inventory.IIAEStackInventory import appeng.util.item.AEItemStack +import appeng.util.prioitylist.OreFilteredList import li.cil.oc.api.machine.{Arguments, Context} import li.cil.oc.api.network.ManagedEnvironment import li.cil.oc.integration.appeng.AEStackFactory -import li.cil.oc.util.DatabaseAccess import li.cil.oc.util.ExtendedArguments._ import li.cil.oc.util.ResultWrapper.result +import li.cil.oc.util.{DatabaseAccess, ReflectionUtils} import net.minecraftforge.common.util.ForgeDirection +import java.lang.invoke.MethodHandle +import java.util.function.Predicate import scala.language.implicitConversions import scala.reflect.ClassTag @@ -91,6 +95,9 @@ object PartEnvironmentBase { } trait PartSharedItemBusBase[PartType <: PartSharedItemBus[_]] extends PartEnvironmentBase[PartType] { + + import PartSharedItemBusBase._ + implicit def tag: ClassTag[PartType] def getSlotSize(part: PartType): Int = @@ -101,6 +108,45 @@ trait PartSharedItemBusBase[PartType <: PartSharedItemBus[_]] extends PartEnviro val part = getPart(side) result(getSlotSize(part)) } + + def getMonitor[T <: IAEStack[T]](part: PartType): IMEMonitor[T] = { + getMonitorHandle.invoke(part).asInstanceOf[IMEMonitor[T]] + } + + def setFilterPredicate(part: PartType, filterPredicate: Predicate[IAEItemStack]): Unit = { + filterPredicateSetterHandle.invoke(part, filterPredicate) + } + + def supportFuzzy(part: PartType): Boolean = { + supportFuzzyHandle.invoke(part).asInstanceOf[Boolean] + } + + def supportOreDict(part: PartType): Boolean = { + supportOreDictHandle.invoke(part).asInstanceOf[Boolean] + } + + def getMySrc(part: PartType): MachineSource = { + mySrcGetterHandle.invoke(part).asInstanceOf[MachineSource] + } + + def getOreFilterPredicate(part: PartType): Predicate[IAEItemStack] = { + val oreFilterString = part.getFilter + var filterPredicate = filterPredicateGetterHandle.invoke(part).asInstanceOf[Predicate[IAEItemStack]] + if (filterPredicate == null) { + filterPredicate = OreFilteredList.makeFilter(oreFilterString) + setFilterPredicate(part, filterPredicate) + } + filterPredicate + } +} + +object PartSharedItemBusBase { + private lazy val getMonitorHandle: MethodHandle = ReflectionUtils.getMethodHandle(classOf[PartSharedItemBus[_]], "getMonitor", allowSuper = false) + private lazy val supportFuzzyHandle: MethodHandle = ReflectionUtils.getMethodHandle(classOf[PartSharedItemBus[_]], "supportFuzzy", allowSuper = false) + private lazy val supportOreDictHandle: MethodHandle = ReflectionUtils.getMethodHandle(classOf[PartSharedItemBus[_]], "supportOreDict", allowSuper = false) + private lazy val filterPredicateGetterHandle: MethodHandle = ReflectionUtils.getFieldGetterHandle(classOf[PartSharedItemBus[_]], "filterPredicate", allowSuper = false) + private lazy val filterPredicateSetterHandle: MethodHandle = ReflectionUtils.getFieldSetterHandle(classOf[PartSharedItemBus[_]], "filterPredicate", allowSuper = false) + private lazy val mySrcGetterHandle: MethodHandle = ReflectionUtils.getFieldGetterHandle(classOf[PartSharedItemBus[_]], "mySrc", allowSuper = false) } object PartItemConfigurablePart { @@ -145,4 +191,4 @@ trait PartItemStorageBusBusBase[PartType <: PartStorageBus] extends PartStorageB object PartItemStorageBusBusBase { implicit def ConfigOps[PartType <: PartStorageBus](env: PartItemStorageBusBusBase[PartType]): PartItemConfigurablePart.ConfigOps[PartType] = new PartItemConfigurablePart.ConfigOps[PartType](env) -} \ No newline at end of file +} diff --git a/src/main/scala/li/cil/oc/util/ReflectionUtils.scala b/src/main/scala/li/cil/oc/util/ReflectionUtils.scala new file mode 100644 index 0000000000..88901b6655 --- /dev/null +++ b/src/main/scala/li/cil/oc/util/ReflectionUtils.scala @@ -0,0 +1,93 @@ +package li.cil.oc.util + +import java.lang.invoke.{MethodHandle, MethodHandles} +import java.lang.reflect.{Field, Method} +import scala.collection.mutable + +object ReflectionUtils { + private case class MethodCacheKey(clazz: Class[_], name: String, allowSuper: Boolean, args: Class[_]*) + private case class FieldCacheKey(clazz: Class[_], name: String, allowSuper: Boolean) + private val methodCache: mutable.HashMap[MethodCacheKey, MethodHandle] = mutable.HashMap.empty; + private val getterCache: mutable.HashMap[FieldCacheKey, MethodHandle] = mutable.HashMap.empty; + private val setterCache: mutable.HashMap[FieldCacheKey, MethodHandle] = mutable.HashMap.empty; + + def getMethodHandle(clazz: Class[_], name: String, allowSuper: Boolean, args: Class[_]*): MethodHandle = { + val key = MethodCacheKey(clazz, name, allowSuper, args: _*) + methodCache.getOrElseUpdate(key, { + var c: Class[_] = clazz + var method: Method = null + while (c != null && method == null) { + try { + method = c.getDeclaredMethod(name, args: _*) + } catch { + case _: NoSuchMethodException => + } + if (allowSuper) c = c.getSuperclass + else c = null + } + if (method == null) { + throw new RuntimeException(s"Method '$name' with params ${args.mkString("(", ", ", ")")} not found in $clazz" + (if (allowSuper) " or its superclasses" else "")) + } + try { + method.setAccessible(true) + MethodHandles.lookup.unreflect(method) + } catch { + case e: IllegalAccessException => throw new RuntimeException(s"Failed to unreflect method '$name' in $clazz", e) + } + }) + } + + def getFieldGetterHandle(clazz: Class[_], name: String, allowSuper: Boolean): MethodHandle = + { + val key = FieldCacheKey(clazz, name, allowSuper) + getterCache.getOrElseUpdate(key, { + var c: Class[_] = clazz + var field: Field = null + while (c != null && field == null) { + try { + field = c.getDeclaredField(name) + } catch { + case _: NoSuchFieldException => + } + if (allowSuper) c = c.getSuperclass + else c = null + } + if (field == null) { + throw new RuntimeException(s"Field '$name' not found in $clazz" + (if (allowSuper) " or its superclasses" else "")) + } + try { + field.setAccessible(true) + MethodHandles.lookup.unreflectGetter(field) + } catch { + case e: IllegalAccessException => throw new RuntimeException(s"Failed to unreflect field '$name' in $clazz", e) + } + }) + } + + def getFieldSetterHandle(clazz: Class[_], name: String, allowSuper: Boolean): MethodHandle = + { + val key = FieldCacheKey(clazz, name, allowSuper) + setterCache.getOrElseUpdate(key, { + var c: Class[_] = clazz + var field: Field = null + while (c != null && field == null) { + try { + field = c.getDeclaredField(name) + } catch { + case _: NoSuchFieldException => + } + if (allowSuper) c = c.getSuperclass + else c = null + } + if (field == null) { + throw new RuntimeException(s"Field '$name' not found in $clazz" + (if (allowSuper) " or its superclasses" else "")) + } + try { + field.setAccessible(true) + MethodHandles.lookup.unreflectSetter(field) + } catch { + case e: IllegalAccessException => throw new RuntimeException(s"Failed to unreflect field '$name' in $clazz", e) + } + }) + } +}