From 94c3cc22869457689316d551a801bb8010e806a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20N=C3=BCcke?= Date: Sun, 26 Jan 2014 15:39:06 +0100 Subject: [PATCH] localized computer error messages and added explicit message when no cpu is installed, closes #96; cleaned up internet card's http logic a bit (since it should only ever be used by one computer at a time anyway it can be simpler than it was), and trying to persist its state (i.e. http request gets restarted after load automatically if it was running) --- assets/opencomputers/lang/de_DE.lang | 11 +- assets/opencomputers/lang/en_US.lang | 11 +- li/cil/oc/common/ConnectionHandler.scala | 6 +- li/cil/oc/common/tileentity/Case.scala | 8 + li/cil/oc/common/tileentity/Computer.scala | 2 +- li/cil/oc/server/PacketHandler.scala | 3 +- li/cil/oc/server/component/InternetCard.scala | 162 +++++++++++------- .../oc/server/component/machine/Machine.scala | 23 +-- 8 files changed, 147 insertions(+), 79 deletions(-) diff --git a/assets/opencomputers/lang/de_DE.lang b/assets/opencomputers/lang/de_DE.lang index fa8ecfdf0..9c953153d 100644 --- a/assets/opencomputers/lang/de_DE.lang +++ b/assets/opencomputers/lang/de_DE.lang @@ -76,8 +76,15 @@ oc:gui.Analyzer.StoredEnergy=§6Gespeicherte Energie§f: %s oc:gui.Analyzer.TotalEnergy=§6Insgesamt gespeicherte Energie§f: %s oc:gui.Analyzer.Users=§6Benutzer§f: %s oc:gui.Chat.WarningLuaFallback=Die native Lua-Implementierung ist nicht verfügbar. Computer können ihren Ausführungszustand nicht speichern. Sie werden automatisch neu starten, sobald ein Chunk neu geladen wird. -oc:gui.chat.WarningPower=Universal Electricity 3 ist nicht verfügbar. Computer, Bildschirme und alle anderen Komponenten werden §lkeine§f Energie benötigen. UE3 wird auch benötigt, um zusätzlich BuildCraft, IndustrialCraft2 und Thermal Expansion zu unterstützen. -oc:gui.chat.WarningProjectRed=Die verwendete Version von Project: Red ist nicht mit OpenComputers kompatibel. Aktualisiere bitte deine Version von Project: Red. +oc:gui.Chat.WarningPower=Universal Electricity 3 ist nicht verfügbar. Computer, Bildschirme und alle anderen Komponenten werden §lkeine§f Energie benötigen. UE3 wird auch benötigt, um zusätzlich BuildCraft, IndustrialCraft2 und Thermal Expansion zu unterstützen. +oc:gui.Chat.WarningProjectRed=Die verwendete Version von Project: Red ist nicht mit OpenComputers kompatibel. Aktualisiere bitte deine Version von Project: Red. +oc:gui.Error.ComponentOverflow=Zu viele Komponenten sind mit dem Computer verbunden. +oc:gui.Error.DaylightCycle=Computer funktionieren nicht, solange die Zeit angehalten ist (Spielregel doDaylightCycle ist aus). +oc:gui.Error.InternalError=Interner Fehler, bitte sieh in der Logdatei nach. Das ist wahrscheinlich ein Bug. +oc:gui.Error.NoCPU=Im Computer ist keine CPU installiert. +oc:gui.Error.NoEnergy=Nicht genug Energie. +oc:gui.Error.NoRAM=Im Computer ist kein RAM installiert. +oc:gui.Error.OutOfMemory=Nicht genug Arbeitsspeicher. oc:gui.Robot.Power=Energie oc:gui.Robot.TurnOff=Ausschalten oc:gui.Robot.TurnOn=Einschalten diff --git a/assets/opencomputers/lang/en_US.lang b/assets/opencomputers/lang/en_US.lang index 0091469cf..4b69443e0 100644 --- a/assets/opencomputers/lang/en_US.lang +++ b/assets/opencomputers/lang/en_US.lang @@ -76,8 +76,15 @@ oc:gui.Analyzer.StoredEnergy=§6Stored energy§f: %s oc:gui.Analyzer.TotalEnergy=§6Total stored energy§f: %s oc:gui.Analyzer.Users=§6Users§f: %s oc:gui.Chat.WarningLuaFallback=Native Lua libraries are not available, computers will not be able to persist their state. They will reboot on chunk reloads. -oc:gui.chat.WarningPower=Universal Electricity 3 is not available. Computers, screens and all other components will §lnot§f require energy. Note that UE3 is also used to additionally support BuildCraft, IndustrialCraft2 and Thermal Expansion. -oc:gui.chat.WarningProjectRed=You are using a version of Project: Red that is incompatible with OpenComputers. Try updating your version of Project: Red. +oc:gui.Chat.WarningPower=Universal Electricity 3 is not available. Computers, screens and all other components will §lnot§f require energy. Note that UE3 is also used to additionally support BuildCraft, IndustrialCraft2 and Thermal Expansion. +oc:gui.Chat.WarningProjectRed=You are using a version of Project: Red that is incompatible with OpenComputers. Try updating your version of Project: Red. +oc:gui.Error.ComponentOverflow=Too many components connected to the computer. +oc:gui.Error.DaylightCycle=Computers don't work while time is frozen (gamerule doDaylightCycle is false). +oc:gui.Error.InternalError=Internal error, please see the log file. This is probably a bug. +oc:gui.Error.NoCPU=No CPU is installed in the computer. +oc:gui.Error.NoEnergy=Not enough energy. +oc:gui.Error.NoRAM=No RAM is installed in the computer. +oc:gui.Error.OutOfMemory=Out of memory. oc:gui.Robot.Power=Energy oc:gui.Robot.TurnOff=Turn off oc:gui.Robot.TurnOn=Turn on diff --git a/li/cil/oc/common/ConnectionHandler.scala b/li/cil/oc/common/ConnectionHandler.scala index 61e25a846..6586a0fdb 100644 --- a/li/cil/oc/common/ConnectionHandler.scala +++ b/li/cil/oc/common/ConnectionHandler.scala @@ -16,13 +16,13 @@ object ConnectionHandler extends IConnectionHandler { if (netHandler.isServerHandler) player match { case p: EntityPlayerMP => if (!LuaStateFactory.isAvailable) { - p.sendChatToPlayer(ChatMessageComponent.createFromText("§aOpenComputers§f: ").addKey(Settings.namespace + "gui.chat.WarningLuaFallback")) + p.sendChatToPlayer(ChatMessageComponent.createFromText("§aOpenComputers§f: ").addKey(Settings.namespace + "gui.Chat.WarningLuaFallback")) } if (ProjectRed.isAvailable && !ProjectRed.isAPIAvailable) { - p.sendChatToPlayer(ChatMessageComponent.createFromText("§aOpenComputers§f: ").addKey(Settings.namespace + "gui.chat.WarningProjectRed")) + p.sendChatToPlayer(ChatMessageComponent.createFromText("§aOpenComputers§f: ").addKey(Settings.namespace + "gui.Chat.WarningProjectRed")) } if (!Settings.get.pureIgnorePower && !Loader.isModLoaded("UniversalElectricity")) { - p.sendChatToPlayer(ChatMessageComponent.createFromText("§aOpenComputers§f: ").addKey(Settings.namespace + "gui.chat.WarningPower")) + p.sendChatToPlayer(ChatMessageComponent.createFromText("§aOpenComputers§f: ").addKey(Settings.namespace + "gui.Chat.WarningPower")) } case _ => } diff --git a/li/cil/oc/common/tileentity/Case.scala b/li/cil/oc/common/tileentity/Case.scala index 9efa51686..38113eb3a 100644 --- a/li/cil/oc/common/tileentity/Case.scala +++ b/li/cil/oc/common/tileentity/Case.scala @@ -31,6 +31,14 @@ class Case(var tier: Int, isRemote: Boolean) extends Computer(isRemote) { case _ => 0 })) + def hasCPU = items.exists { + case Some(stack) => Registry.itemDriverFor(stack) match { + case Some(driver) => driver.slot(stack) == Slot.Processor + case _ => false + } + case _ => false + } + // ----------------------------------------------------------------------- // override def readFromNBT(nbt: NBTTagCompound) { diff --git a/li/cil/oc/common/tileentity/Computer.scala b/li/cil/oc/common/tileentity/Computer.scala index c3cc0730e..066d662f7 100644 --- a/li/cil/oc/common/tileentity/Computer.scala +++ b/li/cil/oc/common/tileentity/Computer.scala @@ -178,7 +178,7 @@ abstract class Computer(isRemote: Boolean) extends Environment with ComponentInv if (computer != null) computer.lastError match { case Some(value) => player.sendChatToPlayer(ChatMessageComponent.createFromTranslationWithSubstitutions( - Settings.namespace + "gui.Analyzer.LastError", value)) + Settings.namespace + "gui.Analyzer.LastError", ChatMessageComponent.createFromTranslationKey(value))) case _ => } val list = users diff --git a/li/cil/oc/server/PacketHandler.scala b/li/cil/oc/server/PacketHandler.scala index 0ad960b1b..8363223a6 100644 --- a/li/cil/oc/server/PacketHandler.scala +++ b/li/cil/oc/server/PacketHandler.scala @@ -9,6 +9,7 @@ import net.minecraftforge.common.{ForgeDirection, DimensionManager} import scala.Some import li.cil.oc.Settings import li.cil.oc.server.component.machine.Machine +import net.minecraft.util.ChatMessageComponent class PacketHandler extends CommonPacketHandler { protected def world(player: Player, dimension: Int) = @@ -49,7 +50,7 @@ class PacketHandler extends CommonPacketHandler { if (!computer.isPaused) { computer.start() computer.lastError match { - case Some(message) => player.addChatMessage(message) + case Some(message) => player.sendChatToPlayer(ChatMessageComponent.createFromTranslationKey(message)) case _ => } } diff --git a/li/cil/oc/server/component/InternetCard.scala b/li/cil/oc/server/component/InternetCard.scala index 1693151bb..252e75c3a 100644 --- a/li/cil/oc/server/component/InternetCard.scala +++ b/li/cil/oc/server/component/InternetCard.scala @@ -8,7 +8,6 @@ import java.util.regex.Matcher import li.cil.oc.Settings import li.cil.oc.api.Network import li.cil.oc.api.network._ -import li.cil.oc.util.ExtendedNBT._ import li.cil.oc.util.ThreadPoolFactory import net.minecraft.nbt.NBTTagCompound import net.minecraft.server.MinecraftServer @@ -22,8 +21,20 @@ class InternetCard(val owner: Context) extends ManagedComponent { protected val connections = mutable.Map.empty[Int, SocketChannel] - // node address -> list per request -> list of signals as (request url, packets) - protected val queues = mutable.Map.empty[String, mutable.Queue[(String, mutable.Queue[Array[Byte]])]] + // For HTTP requests the state switches like so: + // Pre: request == None && queue == None + // request = value + // thread { + // queue = value + // request = None + // } + // while (request == None && queue contains elements) { signal(queue.pop()) } + // queue = None + // Post: request == None && queue == None + + protected var request: Option[(String, Option[String])] = None + + protected var queue: Option[(String, mutable.Queue[Array[Byte]])] = None // ----------------------------------------------------------------------- // @@ -32,10 +43,26 @@ class InternetCard(val owner: Context) extends ManagedComponent { @LuaCallback("request") def request(context: Context, args: Arguments): Array[AnyRef] = { + if (context.address != owner.address) { + throw new IllegalArgumentException("can only be used by the owning computer") + } val address = args.checkString(0) - if (!Settings.get.httpEnabled) return result(false, "http requests are unavailable") - val url = checkAddress(address) + if (!Settings.get.httpEnabled) { + return result(false, "http requests are unavailable") + } val post = if (args.isString(1)) Option(args.checkString(1)) else None + this.synchronized { + if (request.isDefined || queue.isDefined) { + return result(false, "already busy with another request") + } + scheduleRequest(address, post) + } + result(true) + } + + protected def scheduleRequest(address: String, post: Option[String]) { + val url = checkAddress(address) + request = Some((address, post)) InternetCard.threadPool.submit(new Runnable { def run() = try { val proxy = Option(MinecraftServer.getServer.getServerProxy).getOrElse(java.net.Proxy.NO_PROXY) @@ -67,24 +94,34 @@ class InternetCard(val owner: Context) extends ManagedComponent { } } while (count != -1) input.close() - queues.synchronized(queues.getOrElseUpdate(context.address, mutable.Queue.empty) += address -> (mutable.Queue(data.toArray.grouped(Settings.get.maxNetworkPacketSize).toSeq: _*) ++ Iterable(null))) + queue = Some(address -> (mutable.Queue(data.toArray.grouped(Settings.get.maxNetworkPacketSize).toSeq: _*) ++ Iterable(null))) } finally { http.disconnect() } - case other => context.signal("http_response", address, Unit, "connection failed") + case other => owner.signal("http_response", address, Unit, "connection failed") } } catch { case e: FileNotFoundException => - context.signal("http_response", address, Unit, "not found: " + Option(e.getMessage).getOrElse(e.toString)) + owner.signal("http_response", address, Unit, "not found: " + Option(e.getMessage).getOrElse(e.toString)) case _: SocketTimeoutException => - context.signal("http_response", address, Unit, "timeout") + owner.signal("http_response", address, Unit, "timeout") case e: Throwable => - context.signal("http_response", address, Unit, Option(e.getMessage).getOrElse(e.toString)) + owner.signal("http_response", address, Unit, Option(e.getMessage).getOrElse(e.toString)) + } + finally { + InternetCard.this.synchronized { + if (request.isDefined) { + request = None + } + else { + // Got disconnected in the meantime. + queue = None + } + } } }) - result(true) } @LuaCallback(value = "isTcpEnabled", direct = true) @@ -154,37 +191,43 @@ class InternetCard(val owner: Context) extends ManagedComponent { override def update() { super.update() - queues.synchronized { - for ((nodeAddress, queue) <- queues if queue.nonEmpty) { - node.network.node(nodeAddress) match { - case computer: Node => - computer.host match { - case context: Context => - val (address, packets) = queue.front - if (context.signal("http_response", address, packets.front)) { - packets.dequeue() - } - case _ => queue.clear() - } - case _ => queue.clear() + this.synchronized { + if (request.isEmpty && queue.isDefined) { + val (address, packets) = queue.get + if (owner.signal("http_response", address, packets.front)) { + packets.dequeue() + } + if (packets.isEmpty) { + queue = None } - // Remove all responses that have no more packets (usually only the - // first on when it has been processed). - queue.dequeueAll(_._2.isEmpty) } - // Remove all targets that have no more responses. - queues.retain((_, queue) => queue.nonEmpty) } } // ----------------------------------------------------------------------- // + override def onDisconnect(node: Node) { + super.onDisconnect(node) + if (node == this.node) { + for ((_, socket) <- connections) { + socket.close() + } + connections.clear() + request = None + queue = None + } + } + override def onMessage(message: Message) { super.onMessage(message) message.data match { case Array() if (message.name == "computer.stopped" || message.name == "computer.started") && message.source.address == owner.address => connections.values.foreach(_.close()) connections.clear() + InternetCard.this.synchronized { + request = None + queue = None + } case _ => } } @@ -193,44 +236,43 @@ class InternetCard(val owner: Context) extends ManagedComponent { override def load(nbt: NBTTagCompound) { super.load(nbt) - queues.synchronized { - queues.clear() - if (nbt.hasKey("queues")) { - queues ++= nbt.getTagList("queues").iterator[NBTTagCompound].map(nodeNbt => { - val nodeAddress = nodeNbt.getString("nodeAddress") - val responses = mutable.Queue(nodeNbt.getTagList("responses").iterator[NBTTagCompound].map(responseNbt => { - val address = responseNbt.getString("address") - val data = responseNbt.getByteArray("data") - (address, mutable.Queue(data.grouped(Settings.get.maxNetworkPacketSize).toSeq: _*) ++ Iterable(null)) - }): _*) - nodeAddress -> responses - }) + if (nbt.hasKey("url")) { + val address = nbt.getString("url") + val data = nbt.getByteArray("data") + queue = Some(address -> (mutable.Queue(data.grouped(Settings.get.maxNetworkPacketSize).toSeq: _*) ++ Iterable(null))) + } + if (nbt.hasKey("request")) { + val address = nbt.getString("request") + val post = + if (nbt.hasKey("postData")) Option(nbt.getString("postData")) + else None + // Restart request? + if (!queue.isDefined) { + scheduleRequest(address, post) } + // Otherwise this should have been None anyway... } } override def save(nbt: NBTTagCompound) { super.save(nbt) - queues.synchronized { - if (!queues.isEmpty) { - nbt.setNewTagList("queues", queues.toIterable.map( - node => { - val (nodeAddress, responses) = node - val nodeNbt = new NBTTagCompound() - nodeNbt.setString("nodeAddress", nodeAddress) - nodeNbt.setNewTagList("responses", responses.toIterable.map( - response => { - val (address, packets) = response - val responseNbt = new NBTTagCompound() - responseNbt.setString("address", address) - val data = mutable.ArrayBuffer.empty[Byte] - packets.toIterable.dropRight(1).foreach(data.appendAll(_)) - responseNbt.setByteArray("data", data.toArray) - responseNbt - } - )) + this.synchronized { + request match { + case Some((address, data)) => + nbt.setString("request", address) + data match { + case Some(value) => nbt.setString("postData", value) + case _ => } - )) + case _ => + } + queue match { + case Some((address, packets)) => + nbt.setString("url", address) + val data = mutable.ArrayBuffer.empty[Byte] + packets.toIterable.dropRight(1).foreach(data.appendAll(_)) + nbt.setByteArray("data", data.toArray) + case _ => } } } diff --git a/li/cil/oc/server/component/machine/Machine.scala b/li/cil/oc/server/component/machine/Machine.scala index bdd2ea957..96391aa20 100644 --- a/li/cil/oc/server/component/machine/Machine.scala +++ b/li/cil/oc/server/component/machine/Machine.scala @@ -96,11 +96,14 @@ class Machine(val owner: Machine.Owner) extends ManagedComponent with Context wi case Machine.State.Stopped => val rules = owner.world.getWorldInfo.getGameRulesInstance if (rules.hasRule("doDaylightCycle") && !rules.getGameRuleBooleanValue("doDaylightCycle")) { - crash("computers don't work while time is frozen (gamerule doDaylightCycle is false)") + crash(Settings.namespace + "gui.Error.DaylightCycle") false } else if (components.size + addedComponents.size > owner.maxComponents) { - message = Some("too many components") + message = owner match { + case t: tileentity.Case if !t.hasCPU => Some(Settings.namespace + "gui.Error.NoCPU") + case _ => Some(Settings.namespace + "gui.Error.ComponentOverflow") + } false } else if (owner.installedMemory > 0) { @@ -113,12 +116,12 @@ class Machine(val owner: Machine.Owner) extends ManagedComponent with Context wi } } else { - message = Some("not enough energy") + message = Some(Settings.namespace + "gui.Error.NoEnergy") false } } else { - message = Some("no memory installed") + message = Some(Settings.namespace + "gui.Error.NoRAM") false } case Machine.State.Paused if remainingPause > 0 => @@ -269,7 +272,7 @@ class Machine(val owner: Machine.Owner) extends ManagedComponent with Context wi // Component overflow check, crash if too many components are connected, to // avoid confusion on the user's side due to components not showing up. if (components.size > owner.maxComponents) { - crash("too many components") + crash(Settings.namespace + "gui.Error.ComponentOverflow") } // Update world time for time(). @@ -294,11 +297,11 @@ class Machine(val owner: Machine.Owner) extends ManagedComponent with Context wi Machine.State.Stopped => // No power consumption. case Machine.State.Sleeping if remainIdle > 0 && signals.isEmpty => if (!node.tryChangeBuffer(-cost * Settings.get.sleepCostFactor)) { - crash("not enough energy") + crash(Settings.namespace + "gui.Error.NoEnergy") } case _ => if (!node.tryChangeBuffer(-cost)) { - crash("not enough energy") + crash(Settings.namespace + "gui.Error.NoEnergy") } }) } @@ -368,10 +371,10 @@ class Machine(val owner: Machine.Owner) extends ManagedComponent with Context wi } } catch { case e: java.lang.Error if e.getMessage == "not enough memory" => - crash("not enough memory") + crash(Settings.namespace + "gui.Error.OutOfMemory") case e: Throwable => OpenComputers.log.log(Level.WARNING, "Faulty architecture implementation for synchronized calls.", e) - crash("protocol error") + crash(Settings.namespace + "gui.Error.InternalError") } assert(state.top != Machine.State.Running) case _ => // Nothing special to do, just avoid match errors. @@ -733,7 +736,7 @@ class Machine(val owner: Machine.Owner) extends ManagedComponent with Context wi catch { case e: Throwable => OpenComputers.log.log(Level.WARNING, "Architecture's runThreaded threw an error. This should never happen!", e) - crash("kernel panic: architecture threw an exception, see log file") + crash(Settings.namespace + "gui.Error.InternalError") } // Keep track of time spent executing the computer.