diff --git a/src/main/java/li/cil/oc/api/machine/Architecture.java b/src/main/java/li/cil/oc/api/machine/Architecture.java index 43a9c8f9a..ff26e6286 100644 --- a/src/main/java/li/cil/oc/api/machine/Architecture.java +++ b/src/main/java/li/cil/oc/api/machine/Architecture.java @@ -2,6 +2,11 @@ package li.cil.oc.api.machine; import net.minecraft.nbt.NBTTagCompound; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + /** * This interface abstracts away any language specific details for the Machine. *
@@ -11,12 +16,6 @@ import net.minecraft.nbt.NBTTagCompound; * Java Lua architecture (using LuaJ). */ public interface Architecture { - /** - * A display friendly name of the architecture, mainly intended to be used - * when iterating all {@link li.cil.oc.api.Machine#architectures()}. - */ - String name(); - /** * Used to check if the machine is fully initialized. If this is false no * signals for detected components will be generated. Avoids duplicate @@ -133,4 +132,16 @@ public interface Architecture { * @param nbt the tag compound to save to. */ void save(NBTTagCompound nbt); + + /** + * Architectures can be annotated with this to provide a nice display name. + * + * This is used when the name of an architecture has to be displayed to the + * user, such as when cycling architectures on a CPU. + */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.TYPE) + static @interface Name { + String value(); + } } diff --git a/src/main/resources/assets/opencomputers/lang/de_DE.lang b/src/main/resources/assets/opencomputers/lang/de_DE.lang index 76f5d5e17..d3275d568 100644 --- a/src/main/resources/assets/opencomputers/lang/de_DE.lang +++ b/src/main/resources/assets/opencomputers/lang/de_DE.lang @@ -195,6 +195,7 @@ oc:tooltip.CircuitBoard=Mühsam ernährt sich das Eichhörnchen. Wenn es groß w oc:tooltip.ControlUnit=Klingt wichtig, ist es auch. Man baut daraus immerhin CPUs. Wie könnte es da nicht wichtig sein. oc:tooltip.ComponentBus=Diese Erweiterung erlaubt es es Servern, mit noch mehr Komponenten gleichzeitig zu kommunizieren, ähnlich wie CPUs.[nl] Supported components: §f%s§7. oc:tooltip.CPU=Kernstück eines jeden Computers. Die Taktrate hat einen leichten Schatten, aber was kann man von einer Taschensonnenuhr schon erwarten?[nl] Unterstützte Komponenten: §f%s§7. +oc:tooltip.CPU.Architecture=Architektur: §f%s§7 oc:tooltip.CuttingWire=Wird gebraucht, um Tonblöcke in Leiterplattenform zu bekommen. Vermutlich das ineffizienteste Werkzeug in der Geschichte der Menschheit, da es nach einer Verwendung kaputt geht. oc:tooltip.DebugCard=Kreativ-Modus-Gegenstand, erlaubt es die Welt zu manipulieren um das Testen zu erleichtern. Verwendung auf eigene Gefahr. oc:tooltip.Disassembler=Zerlegt Gegenstände in ihre Einzelteile. §lWarnung§7: zurückgewonnene Gegenstände haben eine %s%%-ige Chance beim Extrahieren kaputt zu gehen! diff --git a/src/main/resources/assets/opencomputers/lang/en_US.lang b/src/main/resources/assets/opencomputers/lang/en_US.lang index 682b19743..6d0000d0b 100644 --- a/src/main/resources/assets/opencomputers/lang/en_US.lang +++ b/src/main/resources/assets/opencomputers/lang/en_US.lang @@ -195,6 +195,7 @@ oc:tooltip.CircuitBoard=Now we're getting somewhere. Can be etched to obtain a p oc:tooltip.ControlUnit=This is the unit that... controls... stuff. You need it to build a CPU. So yeah, totally important. oc:tooltip.ComponentBus=This expansion allows servers to communicate with more components at the same time, similar to how CPUs do.[nl] Supported components: §f%s§7. oc:tooltip.CPU=An essential component of all computers. The clock rate is a bit unreliable, but what do you expect when it runs on a pocket sundial?[nl] Supported components: §f%s§7. +oc:tooltip.CPU.Architecture=Architecture: §f%s§7 oc:tooltip.CuttingWire=Used to cut clay blocks into circuit board shape. Breaks after one use, which probably makes it the most inefficient tool ever. oc:tooltip.DebugCard=Creative mode item, allows manipulating the world to make testing easier. Use at your own peril. oc:tooltip.Disassembler=Separates items into their original components. §lWarning§7: returned items have a %s%% chance of breaking in the process! diff --git a/src/main/resources/assets/opencomputers/lua/kernel.lua b/src/main/resources/assets/opencomputers/lua/kernel.lua index 2abcd0708..a69328c66 100644 --- a/src/main/resources/assets/opencomputers/lua/kernel.lua +++ b/src/main/resources/assets/opencomputers/lua/kernel.lua @@ -480,12 +480,13 @@ local function isDirect(address, method) return cachedValue end local methods, reason = spcall(component.methods, address) - if methods then - for name, info in pairs(methods) do - if name == method then - directCache[cacheKey] = info.direct - return info.direct - end + if not methods then + return false + end + for name, info in pairs(methods) do + if name == method then + directCache[cacheKey] = info.direct + return info.direct end end error("no such method", 1) diff --git a/src/main/scala/li/cil/oc/common/Proxy.scala b/src/main/scala/li/cil/oc/common/Proxy.scala index 3dff993d9..f8afd38c6 100644 --- a/src/main/scala/li/cil/oc/common/Proxy.scala +++ b/src/main/scala/li/cil/oc/common/Proxy.scala @@ -62,6 +62,7 @@ class Proxy { api.Machine.LuaArchitecture = if (LuaStateFactory.isAvailable && !Settings.get.forceLuaJ) classOf[NativeLuaArchitecture] else classOf[LuaJLuaArchitecture] + api.Machine.add(api.Machine.LuaArchitecture) api.Network.instance = network.Network if (Mods.ForgeMultipart.isAvailable) { diff --git a/src/main/scala/li/cil/oc/common/item/CPU.scala b/src/main/scala/li/cil/oc/common/item/CPU.scala index 03ab77e72..525866d37 100644 --- a/src/main/scala/li/cil/oc/common/item/CPU.scala +++ b/src/main/scala/li/cil/oc/common/item/CPU.scala @@ -1,6 +1,19 @@ package li.cil.oc.common.item +import java.util + import li.cil.oc.Settings +import li.cil.oc.api +import li.cil.oc.api.machine.Architecture +import li.cil.oc.util.Tooltip +import net.minecraft.entity.player.EntityPlayer +import net.minecraft.item.ItemStack +import net.minecraft.nbt.NBTTagCompound +import net.minecraft.util.ChatComponentTranslation +import net.minecraft.world.World + +import scala.collection.convert.WrapAsScala._ +import scala.language.existentials class CPU(val parent: Delegator, val tier: Int) extends Delegate with ItemTier { override val unlocalizedName = super.unlocalizedName + tier @@ -8,4 +21,49 @@ class CPU(val parent: Delegator, val tier: Int) extends Delegate with ItemTier { override protected def tooltipName = Option(super.unlocalizedName) override protected def tooltipData = Seq(Settings.get.cpuComponentSupport(tier)) + + override protected def tooltipExtended(stack: ItemStack, tooltip: util.List[String]) { + (if (stack.hasTagCompound) { + Option(stack.getTagCompound.getString(Settings.namespace + "archName")) + } + else { + val architectures = allArchitectures + architectures.headOption.map(_._2) + }) match { + case Some(archName) => tooltip.addAll(Tooltip.get("CPU.Architecture", archName)) + case _ => // No architecture. + } + } + + override def onItemRightClick(stack: ItemStack, world: World, player: EntityPlayer) = { + if (player.isSneaking) { + if (!world.isRemote) { + val architectures = allArchitectures + if (architectures.length > 0) { + val currentIndex = if (stack.hasTagCompound) { + val currentArch = stack.getTagCompound.getString(Settings.namespace + "archClass") + architectures.indexWhere(_._1.getName == currentArch) + } + else { + stack.setTagCompound(new NBTTagCompound()) + -1 + } + val index = (currentIndex + 1) % architectures.length + val (archClass, archName) = architectures(index) + stack.getTagCompound.setString(Settings.namespace + "archClass", archClass.getName) + stack.getTagCompound.setString(Settings.namespace + "archName", archName) + player.addChatMessage(new ChatComponentTranslation(Settings.namespace + "tooltip.CPU.Architecture", archName)) + } + player.swingItem() + } + } + stack + } + + private def allArchitectures = api.Machine.architectures.map { arch => + arch.getAnnotation(classOf[Architecture.Name]) match { + case annotation: Architecture.Name => (arch, annotation.value) + case _ => (arch, arch.getSimpleName) + } + }.toArray } diff --git a/src/main/scala/li/cil/oc/server/driver/item/CPU.scala b/src/main/scala/li/cil/oc/server/driver/item/CPU.scala index 8f80afc17..e923dd217 100644 --- a/src/main/scala/li/cil/oc/server/driver/item/CPU.scala +++ b/src/main/scala/li/cil/oc/server/driver/item/CPU.scala @@ -1,15 +1,18 @@ package li.cil.oc.server.driver.item +import li.cil.oc.OpenComputers import li.cil.oc.Settings import li.cil.oc.api -import li.cil.oc.api.Machine import li.cil.oc.api.driver.EnvironmentHost import li.cil.oc.api.driver.item.Processor +import li.cil.oc.api.machine.Architecture import li.cil.oc.common.Slot import li.cil.oc.common.init.Items import li.cil.oc.common.item import net.minecraft.item.ItemStack +import scala.collection.convert.WrapAsScala._ + object CPU extends Item with Processor { override def worksWith(stack: ItemStack) = isOneOf(stack, api.Items.get("cpu1"), api.Items.get("cpu2"), api.Items.get("cpu3")) @@ -30,5 +33,15 @@ object CPU extends Item with Processor { case _ => 0 } - override def architecture(stack: ItemStack) = Machine.LuaArchitecture + override def architecture(stack: ItemStack) = { + if (stack.hasTagCompound) { + val archClass = stack.getTagCompound.getString(Settings.namespace + "archClass") + try Class.forName(archClass).asSubclass(classOf[Architecture]) catch { + case t: Throwable => + OpenComputers.log.warn("Failed getting class for CPU architecture.", t) + null + } + } + else api.Machine.architectures.headOption.orNull + } } diff --git a/src/main/scala/li/cil/oc/server/machine/Machine.scala b/src/main/scala/li/cil/oc/server/machine/Machine.scala index eedd27b04..02abce1bd 100644 --- a/src/main/scala/li/cil/oc/server/machine/Machine.scala +++ b/src/main/scala/li/cil/oc/server/machine/Machine.scala @@ -58,6 +58,9 @@ class Machine(val host: MachineHost) extends prefab.ManagedEnvironment with mach @volatile private var callBudget = 0.0 + // We want to ignore the call limit in synchronized calls to avoid errors. + private var inSynchronizedCall = false + // ----------------------------------------------------------------------- // var worldTime = 0L // Game-world time for os.time(). @@ -268,7 +271,7 @@ class Machine(val host: MachineHost) extends prefab.ManagedEnvironment with mach case _ => throw new NoSuchMethodException() } - private def checkLimit(limit: Int) { + private def checkLimit(limit: Int): Unit = if (!inSynchronizedCall) { val callCost = math.max(1.0 / limit, 0.001) if (callCost >= callBudget) { throw new LimitReachedException() @@ -421,16 +424,13 @@ class Machine(val host: MachineHost) extends prefab.ManagedEnvironment with mach } // Perform a synchronized call (message sending). case Machine.State.SynchronizedCall => - // Reset direct call budget again, just to be on the safe side... - // Theoretically it'd be possible for the executor to do some direct - // calls between the clear and the state check, which could in turn - // make this synchronized call fail due the limit still being maxed. - callBudget = maxCallBudget // We switch into running state, since we'll behave as though the call // were performed from our executor thread. switchTo(Machine.State.Running) try { + inSynchronizedCall = true architecture.runSynchronized() + inSynchronizedCall = false // Check if the callback called pause() or stop(). state.top match { case Machine.State.Running => @@ -451,6 +451,9 @@ class Machine(val host: MachineHost) extends prefab.ManagedEnvironment with mach OpenComputers.log.warn("Faulty architecture implementation for synchronized calls.", e) crash("gui.Error.InternalError") } + finally { + inSynchronizedCall = false + } assert(state.top != Machine.State.Running) case _ => // Nothing special to do, just avoid match errors. @@ -871,7 +874,8 @@ class Machine(val host: MachineHost) extends prefab.ManagedEnvironment with mach } object Machine extends MachineAPI { - val checked = mutable.Set.empty[Class[_ <: Architecture]] + // Keep registration order, to allow deterministic iteration of the architectures. + val checked = mutable.LinkedHashSet.empty[Class[_ <: Architecture]] override def add(architecture: Class[_ <: Architecture]) { if (!checked.contains(architecture)) { @@ -885,7 +889,7 @@ object Machine extends MachineAPI { } } - override def architectures() = scala.collection.convert.WrapAsJava.asJavaIterable(checked) + override def architectures = scala.collection.convert.WrapAsJava.asJavaIterable(checked) override def create(host: MachineHost) = new Machine(host) diff --git a/src/main/scala/li/cil/oc/server/machine/luac/NativeLuaArchitecture.scala b/src/main/scala/li/cil/oc/server/machine/luac/NativeLuaArchitecture.scala index 66bc22b04..5d246ca2f 100644 --- a/src/main/scala/li/cil/oc/server/machine/luac/NativeLuaArchitecture.scala +++ b/src/main/scala/li/cil/oc/server/machine/luac/NativeLuaArchitecture.scala @@ -4,6 +4,9 @@ import java.io.FileNotFoundException import java.io.IOException import com.google.common.base.Strings +import li.cil.oc.OpenComputers +import li.cil.oc.Settings +import li.cil.oc.api import li.cil.oc.api.machine.Architecture import li.cil.oc.api.machine.ExecutionResult import li.cil.oc.api.machine.LimitReachedException @@ -11,12 +14,10 @@ import li.cil.oc.common.SaveHandler import li.cil.oc.server.machine.Machine import li.cil.oc.util.ExtendedLuaState.extendLuaState import li.cil.oc.util.LuaStateFactory -import li.cil.oc.OpenComputers -import li.cil.oc.Settings -import li.cil.oc.api import li.cil.repack.com.naef.jnlua._ import net.minecraft.nbt.NBTTagCompound +@Architecture.Name("Lua") class NativeLuaArchitecture(val machine: api.machine.Machine) extends Architecture { private[machine] var lua: LuaState = null @@ -127,8 +128,6 @@ class NativeLuaArchitecture(val machine: api.machine.Machine) extends Architectu // ----------------------------------------------------------------------- // - override def name() = "Lua" - override def isInitialized = kernelMemory > 0 override def recomputeMemory() = Option(lua) match { diff --git a/src/main/scala/li/cil/oc/server/machine/luaj/LuaJLuaArchitecture.scala b/src/main/scala/li/cil/oc/server/machine/luaj/LuaJLuaArchitecture.scala index beeed8d38..ee6a5e77d 100644 --- a/src/main/scala/li/cil/oc/server/machine/luaj/LuaJLuaArchitecture.scala +++ b/src/main/scala/li/cil/oc/server/machine/luaj/LuaJLuaArchitecture.scala @@ -4,19 +4,20 @@ import java.io.FileNotFoundException import java.io.IOException import com.google.common.base.Strings +import li.cil.oc.OpenComputers +import li.cil.oc.Settings +import li.cil.oc.api import li.cil.oc.api.machine.Architecture import li.cil.oc.api.machine.ExecutionResult import li.cil.oc.api.machine.LimitReachedException import li.cil.oc.server.machine.Machine import li.cil.oc.util.ScalaClosure import li.cil.oc.util.ScalaClosure._ -import li.cil.oc.OpenComputers -import li.cil.oc.Settings -import li.cil.oc.api import li.cil.repack.org.luaj.vm2._ import li.cil.repack.org.luaj.vm2.lib.jse.JsePlatform import net.minecraft.nbt.NBTTagCompound +@Architecture.Name("LuaJ") class LuaJLuaArchitecture(val machine: api.machine.Machine) extends Architecture { private[machine] var lua: Globals = _ @@ -90,8 +91,6 @@ class LuaJLuaArchitecture(val machine: api.machine.Machine) extends Architecture // ----------------------------------------------------------------------- // - override def name() = "LuaJ" - override def isInitialized = doneWithInitRun override def recomputeMemory() = memory = machine.host.installedMemory