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

This commit is contained in:
Florian Nücke 2013-11-04 13:27:05 +01:00
parent 301cec06d6
commit 49ced36d5f
5 changed files with 96 additions and 43 deletions

View File

@ -7,10 +7,29 @@ object Config {
val savePath = "opencomputers/" val savePath = "opencomputers/"
val scriptPath = "/assets/" + resourceDomain + "/lua/" val scriptPath = "/assets/" + resourceDomain + "/lua/"
// ----------------------------------------------------------------------- //
val screenResolutionsByTier = Array((50, 16), (80, 25), (160, 50)) 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 var blockRenderId = 0
// ----------------------------------------------------------------------- // // ----------------------------------------------------------------------- //

View File

@ -4,7 +4,7 @@ import li.cil.oc.api.Persistable
import li.cil.oc.api.network.Visibility import li.cil.oc.api.network.Visibility
import li.cil.oc.common.{tileentity, component} import li.cil.oc.common.{tileentity, component}
import li.cil.oc.util.TextBuffer import li.cil.oc.util.TextBuffer
import li.cil.oc.{util, api} import li.cil.oc.{Config, util, api}
import net.minecraft.nbt.NBTTagCompound import net.minecraft.nbt.NBTTagCompound
class Screen(val owner: Screen.Environment, val maxResolution: (Int, Int)) extends Persistable { 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) = val (x, truncated) =
if (col < 0) (0, s.substring(-col)) if (col < 0) (0, s.substring(-col))
else (col, s.substring(0, s.length min buffer.width)) else (col, s.substring(0, s.length min buffer.width))
if (buffer.set(x, row, truncated)) if (consumePower(truncated.length, Config.screenSetCost))
owner.onScreenSet(x, row, truncated) if (buffer.set(x, row, truncated))
owner.onScreenSet(x, row, truncated)
} }
def fill(col: Int, row: Int, w: Int, h: Int, c: Char) = def fill(col: Int, row: Int, w: Int, h: Int, c: Char) =
if (buffer.fill(col, row, w, h, c)) if (consumePower(w * h, if (c == ' ') Config.screenClearCost else Config.screenFillCost))
owner.onScreenFill(col, row, w, h, c) 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) = def copy(col: Int, row: Int, w: Int, h: Int, tx: Int, ty: Int) =
if (buffer.copy(col, row, w, h, tx, ty)) if (consumePower(w * h, Config.screenCopyCost))
owner.onScreenCopy(col, row, w, h, tx, ty) 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) buffer.writeToNBT(screenNbt)
nbt.setCompoundTag("oc.screen", screenNbt) nbt.setCompoundTag("oc.screen", screenNbt)
} }
// ----------------------------------------------------------------------- //
private def consumePower(n: Double, cost: Double) =
owner.node == null || owner.node.changeBuffer(-n * cost)
} }
object Screen { 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) { def onScreenResolutionChange(w: Int, h: Int) {
if (node != null) if (node != null)
node.sendToReachable("computer.signal", "screen_resized", Int.box(w), Int.box(h)) node.sendToReachable("computer.signal", "screen_resized", Int.box(w), Int.box(h))
} }
def onScreenSet(col: Int, row: Int, s: String) {} 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) {}
} }
} }

View File

@ -1,6 +1,5 @@
package li.cil.oc.common.tileentity package li.cil.oc.common.tileentity
import java.util.concurrent.atomic.AtomicBoolean
import li.cil.oc.api.Network import li.cil.oc.api.Network
import li.cil.oc.api.driver.Slot import li.cil.oc.api.driver.Slot
import li.cil.oc.client.{PacketSender => ClientPacketSender} 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 private var isRunning = false
@ -30,7 +31,10 @@ class Computer(isClient: Boolean) extends Rotatable with ComputerEnvironment wit
def world = worldObj 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 // too, avoiding issues of missing nodes (e.g. in the GPU which would
// otherwise loose track of its screen). // otherwise loose track of its screen).
instance.update() instance.update()
updateRedstoneInput() val (powerRequired, needsSaving) = this.synchronized {
if (hasChanged.get) 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) worldObj.markTileEntityChunkModified(xCoord, yCoord, zCoord, this)
if (isRunning != instance.isRunning) if (isRunning != instance.isRunning)
ServerPacketSender.sendComputerState(this, instance.isRunning) ServerPacketSender.sendComputerState(this, instance.isRunning)
isRunning = instance.isRunning isRunning = instance.isRunning
updateRedstoneInput()
} }
for (component <- components) component match { for (component <- components) component match {

View File

@ -28,10 +28,8 @@ class PowerDistributor extends Rotatable with Environment {
if (node != null && node.network == null) { if (node != null && node.network == null) {
Network.joinOrCreateNetwork(worldObj, xCoord, yCoord, zCoord) Network.joinOrCreateNetwork(worldObj, xCoord, yCoord, zCoord)
} }
if (!worldObj.isRemote && connectors.exists(_.dirty) && computeAverage()) { if (!worldObj.isRemote && connectors.exists(_.dirty))
// Adjust buffer fill ratio for all buffers to average. balance()
connectors.foreach(c => c.buffer = c.bufferSize * average)
}
} }
override def validate() { override def validate() {
@ -51,7 +49,7 @@ class PowerDistributor extends Rotatable with Environment {
else node match { else node match {
case connector: Connector => case connector: Connector =>
connectors -= connector connectors -= connector
computeAverage() balance()
case _ => node.host match { case _ => node.host match {
case distributor: PowerDistributor => distributors -= distributor case distributor: PowerDistributor => distributors -= distributor
case _ => case _ =>
@ -69,7 +67,7 @@ class PowerDistributor extends Rotatable with Environment {
case _ => case _ =>
} }
} }
computeAverage() balance()
} }
else node match { else node match {
case connector: Connector => connectors += connector 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. // Computer average fill ratio of all buffers.
val (minRelativeBuffer, maxRelativeBuffer, sumBuffer, sumBufferSize) = val (minRelativeBuffer, maxRelativeBuffer, sumBuffer, sumBufferSize) =
connectors.foldRight((1.0, 0.0, 0.0, 0.0))((c, acc) => { 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) 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)
}
} }
} }

View File

@ -106,25 +106,27 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl
// ----------------------------------------------------------------------- // // ----------------------------------------------------------------------- //
def start() = stateMonitor.synchronized( def start() = stateMonitor.synchronized(owner.node.network != null &&
(owner.node.network != null && state == Computer.State.Stopped) && init() && { state == Computer.State.Stopped &&
// Initial state. Will be switched to State.Yielded in the next update() owner.installedMemory > 0 &&
// due to the signals queue not being empty ( init() && {
state = Computer.State.Suspended // 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(). // Remember when we started, for os.clock().
timeStarted = owner.world.getWorldInfo.getWorldTotalTime timeStarted = owner.world.getWorldInfo.getWorldTotalTime
// Mark state change in owner, to send it to clients. // Mark state change in owner, to send it to clients.
owner.markAsChanged() owner.markAsChanged(8) // Initial power required to start.
// Push a dummy signal to get the computer going. // Push a dummy signal to get the computer going.
signal("dummy") signal("dummy")
// All green, computer started successfully. // All green, computer started successfully.
owner.node.sendToReachable("computer.started") owner.node.sendToReachable("computer.started")
true true
}) })
def stop() = stateMonitor.synchronized(state match { def stop() = stateMonitor.synchronized(state match {
case Computer.State.Stopped => false // Nothing to do. 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 sleepUntil = Long.MaxValue
// Mark state change in owner, to send it to clients. // 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 { } match {
case Computer.State.SynchronizedReturn => true case Computer.State.SynchronizedReturn => true
case Computer.State.Yielded | Computer.State.Sleeping => false 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 => case s =>
OpenComputers.log.warning("Running computer from invalid state " + s.toString + ". This is a bug!") OpenComputers.log.warning("Running computer from invalid state " + s.toString + ". This is a bug!")
close() close()
@ -992,7 +998,8 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl
case _ => case _ =>
lua.resume(1, 0) 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 // Check if this was the first run, meaning the one used for initializing
// the kernel (loading the libs, establishing a memory baseline). // 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. // 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. // The kernel thread returned. If it threw we'd we in the catch below.
else { else {
@ -1119,8 +1126,10 @@ object Computer {
* <p/> * <p/>
* This is called asynchronously from the Computer's executor thread, so the * 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. * 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
// ----------------------------------------------------------------------- // // ----------------------------------------------------------------------- //