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 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
// ----------------------------------------------------------------------- //

View File

@ -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) {}
}
}

View File

@ -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 {

View File

@ -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)
}
}
}

View File

@ -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 {
* <p/>
* 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
// ----------------------------------------------------------------------- //