From 49ced36d5f9c93d1b10bf3af87d5877bf0c1f6fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20N=C3=BCcke?= Date: Mon, 4 Nov 2013 13:27:05 +0100 Subject: [PATCH] computers need power to run (base level + elapsed cpu time); screens need power to change their display, different costs per operation, depends on number of 'pixels' changed; fixed distributors not balancing buffers if attached to existing network without distributor --- li/cil/oc/Config.scala | 19 ++++++++ li/cil/oc/common/component/Screen.scala | 30 +++++++----- li/cil/oc/common/tileentity/Computer.scala | 26 ++++++++-- .../common/tileentity/PowerDistributor.scala | 17 +++---- li/cil/oc/server/component/Computer.scala | 47 +++++++++++-------- 5 files changed, 96 insertions(+), 43 deletions(-) diff --git a/li/cil/oc/Config.scala b/li/cil/oc/Config.scala index 7ef707471..0d1be4377 100644 --- a/li/cil/oc/Config.scala +++ b/li/cil/oc/Config.scala @@ -7,10 +7,29 @@ object Config { val savePath = "opencomputers/" val scriptPath = "/assets/" + resourceDomain + "/lua/" + // ----------------------------------------------------------------------- // + val screenResolutionsByTier = Array((50, 16), (80, 25), (160, 50)) // ----------------------------------------------------------------------- // + // Power it takes to run a computer at 100% CPU time for one second. + val cpuTimeCost = 256 + + // Power it takes to change a single pixel via the set command. + val screenSetCost = 1.0 / 20 + + // Power it takes to change a single pixel via the fill command. + val screenFillCost = 1.0 / 180 + + // Power it takes to change a single pixel to blank via the fill command. + val screenClearCost = 1.0 / 200 + + // Power it takes to move a single pixel via the copy command. + val screenCopyCost = 1.0 / 160 + + // ----------------------------------------------------------------------- // + var blockRenderId = 0 // ----------------------------------------------------------------------- // diff --git a/li/cil/oc/common/component/Screen.scala b/li/cil/oc/common/component/Screen.scala index 65e5e45ec..51fe01892 100644 --- a/li/cil/oc/common/component/Screen.scala +++ b/li/cil/oc/common/component/Screen.scala @@ -4,7 +4,7 @@ import li.cil.oc.api.Persistable import li.cil.oc.api.network.Visibility import li.cil.oc.common.{tileentity, component} import li.cil.oc.util.TextBuffer -import li.cil.oc.{util, api} +import li.cil.oc.{Config, util, api} import net.minecraft.nbt.NBTTagCompound class Screen(val owner: Screen.Environment, val maxResolution: (Int, Int)) extends Persistable { @@ -36,17 +36,20 @@ class Screen(val owner: Screen.Environment, val maxResolution: (Int, Int)) exten val (x, truncated) = if (col < 0) (0, s.substring(-col)) else (col, s.substring(0, s.length min buffer.width)) - if (buffer.set(x, row, truncated)) - owner.onScreenSet(x, row, truncated) + if (consumePower(truncated.length, Config.screenSetCost)) + if (buffer.set(x, row, truncated)) + owner.onScreenSet(x, row, truncated) } def fill(col: Int, row: Int, w: Int, h: Int, c: Char) = - if (buffer.fill(col, row, w, h, c)) - owner.onScreenFill(col, row, w, h, c) + if (consumePower(w * h, if (c == ' ') Config.screenClearCost else Config.screenFillCost)) + if (buffer.fill(col, row, w, h, c)) + owner.onScreenFill(col, row, w, h, c) def copy(col: Int, row: Int, w: Int, h: Int, tx: Int, ty: Int) = - if (buffer.copy(col, row, w, h, tx, ty)) - owner.onScreenCopy(col, row, w, h, tx, ty) + if (consumePower(w * h, Config.screenCopyCost)) + if (buffer.copy(col, row, w, h, tx, ty)) + owner.onScreenCopy(col, row, w, h, tx, ty) // ----------------------------------------------------------------------- // @@ -59,6 +62,11 @@ class Screen(val owner: Screen.Environment, val maxResolution: (Int, Int)) exten buffer.writeToNBT(screenNbt) nbt.setCompoundTag("oc.screen", screenNbt) } + + // ----------------------------------------------------------------------- // + + private def consumePower(n: Double, cost: Double) = + owner.node == null || owner.node.changeBuffer(-n * cost) } object Screen { @@ -89,16 +97,16 @@ object Screen { // ----------------------------------------------------------------------- // + def onScreenCopy(col: Int, row: Int, w: Int, h: Int, tx: Int, ty: Int) {} + + def onScreenFill(col: Int, row: Int, w: Int, h: Int, c: Char) {} + def onScreenResolutionChange(w: Int, h: Int) { if (node != null) node.sendToReachable("computer.signal", "screen_resized", Int.box(w), Int.box(h)) } def onScreenSet(col: Int, row: Int, s: String) {} - - def onScreenFill(col: Int, row: Int, w: Int, h: Int, c: Char) {} - - def onScreenCopy(col: Int, row: Int, w: Int, h: Int, tx: Int, ty: Int) {} } } \ No newline at end of file diff --git a/li/cil/oc/common/tileentity/Computer.scala b/li/cil/oc/common/tileentity/Computer.scala index 1393ea241..1807b67bc 100644 --- a/li/cil/oc/common/tileentity/Computer.scala +++ b/li/cil/oc/common/tileentity/Computer.scala @@ -1,6 +1,5 @@ package li.cil.oc.common.tileentity -import java.util.concurrent.atomic.AtomicBoolean import li.cil.oc.api.Network import li.cil.oc.api.driver.Slot import li.cil.oc.client.{PacketSender => ClientPacketSender} @@ -20,7 +19,9 @@ class Computer(isClient: Boolean) extends Rotatable with ComputerEnvironment wit // ----------------------------------------------------------------------- // - private val hasChanged = new AtomicBoolean(true) // For `markChanged`. + private var powerConsumed = 0.0 + + private var hasChanged = false private var isRunning = false @@ -30,7 +31,10 @@ class Computer(isClient: Boolean) extends Rotatable with ComputerEnvironment wit def world = worldObj - def markAsChanged() = hasChanged.set(true) + def markAsChanged(power: Double) = this.synchronized { + powerConsumed = (powerConsumed + power) max 0 + hasChanged = true + } // ----------------------------------------------------------------------- // @@ -72,12 +76,24 @@ class Computer(isClient: Boolean) extends Rotatable with ComputerEnvironment wit // too, avoiding issues of missing nodes (e.g. in the GPU which would // otherwise loose track of its screen). instance.update() - updateRedstoneInput() - if (hasChanged.get) + val (powerRequired, needsSaving) = this.synchronized { + val a = powerConsumed + 0.05 + val b = hasChanged + powerConsumed = 0 + hasChanged = false + (a, b) + } + if (isRunning && !node.changeBuffer(-powerRequired)) { + // TODO try to print to screen? sound effect? particle effect? + println("not enough power, shutting down... " + powerRequired) + turnOff() + } + if (needsSaving) worldObj.markTileEntityChunkModified(xCoord, yCoord, zCoord, this) if (isRunning != instance.isRunning) ServerPacketSender.sendComputerState(this, instance.isRunning) isRunning = instance.isRunning + updateRedstoneInput() } for (component <- components) component match { diff --git a/li/cil/oc/common/tileentity/PowerDistributor.scala b/li/cil/oc/common/tileentity/PowerDistributor.scala index 46a0cd5df..8e1959f16 100644 --- a/li/cil/oc/common/tileentity/PowerDistributor.scala +++ b/li/cil/oc/common/tileentity/PowerDistributor.scala @@ -28,10 +28,8 @@ class PowerDistributor extends Rotatable with Environment { if (node != null && node.network == null) { Network.joinOrCreateNetwork(worldObj, xCoord, yCoord, zCoord) } - if (!worldObj.isRemote && connectors.exists(_.dirty) && computeAverage()) { - // Adjust buffer fill ratio for all buffers to average. - connectors.foreach(c => c.buffer = c.bufferSize * average) - } + if (!worldObj.isRemote && connectors.exists(_.dirty)) + balance() } override def validate() { @@ -51,7 +49,7 @@ class PowerDistributor extends Rotatable with Environment { else node match { case connector: Connector => connectors -= connector - computeAverage() + balance() case _ => node.host match { case distributor: PowerDistributor => distributors -= distributor case _ => @@ -69,7 +67,7 @@ class PowerDistributor extends Rotatable with Environment { case _ => } } - computeAverage() + balance() } else node match { case connector: Connector => connectors += connector @@ -94,7 +92,7 @@ class PowerDistributor extends Rotatable with Environment { // ----------------------------------------------------------------------- // - private def computeAverage() = { + private def balance() { // Computer average fill ratio of all buffers. val (minRelativeBuffer, maxRelativeBuffer, sumBuffer, sumBufferSize) = connectors.foldRight((1.0, 0.0, 0.0, 0.0))((c, acc) => { @@ -111,6 +109,9 @@ class PowerDistributor extends Rotatable with Environment { ServerPacketSender.sendPowerState(distributor) } } - maxRelativeBuffer - minRelativeBuffer > 10e-4 + if (maxRelativeBuffer - minRelativeBuffer > 10e-4) { + // Adjust buffer fill ratio for all buffers to average. + connectors.foreach(c => c.buffer = c.bufferSize * average) + } } } diff --git a/li/cil/oc/server/component/Computer.scala b/li/cil/oc/server/component/Computer.scala index bb45fc066..0d6e2364a 100644 --- a/li/cil/oc/server/component/Computer.scala +++ b/li/cil/oc/server/component/Computer.scala @@ -106,25 +106,27 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl // ----------------------------------------------------------------------- // - def start() = stateMonitor.synchronized( - (owner.node.network != null && state == Computer.State.Stopped) && init() && { - // Initial state. Will be switched to State.Yielded in the next update() - // due to the signals queue not being empty ( - state = Computer.State.Suspended + def start() = stateMonitor.synchronized(owner.node.network != null && + state == Computer.State.Stopped && + owner.installedMemory > 0 && + init() && { + // Initial state. Will be switched to State.Yielded in the next update() + // due to the signals queue not being empty ( + state = Computer.State.Suspended - // Remember when we started, for os.clock(). - timeStarted = owner.world.getWorldInfo.getWorldTotalTime + // Remember when we started, for os.clock(). + timeStarted = owner.world.getWorldInfo.getWorldTotalTime - // Mark state change in owner, to send it to clients. - owner.markAsChanged() + // Mark state change in owner, to send it to clients. + owner.markAsChanged(8) // Initial power required to start. - // Push a dummy signal to get the computer going. - signal("dummy") + // Push a dummy signal to get the computer going. + signal("dummy") - // All green, computer started successfully. - owner.node.sendToReachable("computer.started") - true - }) + // All green, computer started successfully. + owner.node.sendToReachable("computer.started") + true + }) def stop() = stateMonitor.synchronized(state match { case Computer.State.Stopped => false // Nothing to do. @@ -925,7 +927,7 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl sleepUntil = Long.MaxValue // Mark state change in owner, to send it to clients. - owner.markAsChanged() + owner.markAsChanged(Double.NegativeInfinity) }) // ----------------------------------------------------------------------- // @@ -957,6 +959,10 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl } match { case Computer.State.SynchronizedReturn => true case Computer.State.Yielded | Computer.State.Sleeping => false + case Computer.State.Stopping => + // stop() was called directly after start(), e.g. due to lack of power. + close() + return case s => OpenComputers.log.warning("Running computer from invalid state " + s.toString + ". This is a bug!") close() @@ -992,7 +998,8 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl case _ => lua.resume(1, 0) } - cpuTime += System.nanoTime() - cpuStart + val runtime = System.nanoTime() - cpuStart + cpuTime += runtime // Check if this was the first run, meaning the one used for initializing // the kernel (loading the libs, establishing a memory baseline). @@ -1051,7 +1058,7 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl } // State has inevitably changed, mark as changed to save again. - owner.markAsChanged() + owner.markAsChanged(Config.cpuTimeCost.toDouble * runtime / 1000 / 1000 / 1000) } // The kernel thread returned. If it threw we'd we in the catch below. else { @@ -1119,8 +1126,10 @@ object Computer { *

* This is called asynchronously from the Computer's executor thread, so the * computer's owner must make sure to handle this in a synchronized fashion. + * + * @param power the power that should be consumed. */ - def markAsChanged(): Unit + def markAsChanged(power: Double): Unit // ----------------------------------------------------------------------- //