cleaned up synchronization in connector/distributor, i think; pausing computer in save to make sure it won't continue running until the whole world finished saving; started work on charger (robot loading block); reworked connector interface, changeBuffer now returns the delta that could not be applied and added new method tryChangeBuffer that will only apply the full delta or nothing, and return true if it worked, false otherwise

This commit is contained in:
Florian Nücke 2013-11-26 14:35:14 +01:00
parent f5e1f09af3
commit c52e18a87b
17 changed files with 251 additions and 90 deletions

View File

@ -71,11 +71,11 @@ class Settings(config: Config) {
val ratioBuildCraft = config.getDouble("power.ratioBuildCraft").toFloat val ratioBuildCraft = config.getDouble("power.ratioBuildCraft").toFloat
val ratioIndustrialCraft2 = config.getDouble("power.ratioIndustrialCraft2").toFloat val ratioIndustrialCraft2 = config.getDouble("power.ratioIndustrialCraft2").toFloat
val ratioUniversalElectricity = config.getDouble("power.ratioUniversalElectricity").toFloat val ratioUniversalElectricity = config.getDouble("power.ratioUniversalElectricity").toFloat
val chargeRate = config.getDouble("power.chargerChargeRate")
// power.buffer // power.buffer
val bufferCapacitor = config.getDouble("power.buffer.capacitor") max 0 val bufferCapacitor = config.getDouble("power.buffer.capacitor") max 0
val bufferCapacitorAdjacencyBonus = config.getDouble("power.buffer.capacitorAdjacencyBonus") max 0 val bufferCapacitorAdjacencyBonus = config.getDouble("power.buffer.capacitorAdjacencyBonus") max 0
val bufferChargingStation = 50000.0
val bufferRobot = config.getDouble("power.buffer.robot") max 0 val bufferRobot = config.getDouble("power.buffer.robot") max 0
// power.cost // power.cost

View File

@ -53,24 +53,30 @@ public interface Connector extends Node {
* a program tries to display text on it. For running costs just apply the * a program tries to display text on it. For running costs just apply the
* same delta each tick. * same delta each tick.
* <p/> * <p/>
* For negative values, if there is not enough energy stored in the buffer * If the specified delta cannot be completely applied to the buffer, the
* this will return <tt>false</tt>, and the operation depending on that * remaining delta will be returned. This means that for negative values
* energy should fail - what energy there is will still be consumed, though! * a part of the energy will have been consumed, though.
* <p/> * <p/>
* For positive values, if there is a buffer overflow due to the added * If there is enough energy or no overflow this will return <tt>0</tt>.
* energy the surplus will be lost and this will return <tt>false</tt>.
* <p/>
* If there is enough energy or no overflow this will return <tt>true</tt>.
* <p/> * <p/>
* Keep in mind that this change is applied to the <em>global</em> buffer, * Keep in mind that this change is applied to the <em>global</em> buffer,
* i.e. energy from multiple buffers may be consumed / multiple buffers may * i.e. energy from multiple buffers may be consumed / multiple buffers may
* be filled. The buffer for which this method is called (i.e. this node * be filled. The buffer for which this method is called (i.e. this node
* instance) will be prioritized, though. * instance) will be prioritized, though.
* *
* @param delta the amount of energy to consume or make available. * @param delta the amount of energy to consume or store.
* @return whether the energy could be consumed or stored. * @return the remainder of the delta that could not be applied.
*/ */
boolean changeBuffer(double delta); double changeBuffer(double delta);
/**
* Like {@link #changeBuffer}, but will only store/consume the specified
* amount of energy if there is enough capacity/energy available.
*
* @param delta the amount of energy to consume or store.
* @return <tt>true</tt> if the energy was successfully consumed or stored.
*/
boolean tryChangeBuffer(double delta);
/** /**
* Change the size of the connectors local buffer. * Change the size of the connectors local buffer.

View File

@ -0,0 +1,37 @@
package li.cil.oc.common.block
import java.util
import li.cil.oc.Settings
import li.cil.oc.common.tileentity
import li.cil.oc.util.Tooltip
import net.minecraft.client.renderer.texture.IconRegister
import net.minecraft.entity.player.EntityPlayer
import net.minecraft.util.Icon
import net.minecraft.world.World
import net.minecraftforge.common.ForgeDirection
class Charger(val parent: SimpleDelegator) extends SimpleDelegate {
val unlocalizedName = "Charger"
var icon: Icon = null
override def addInformation(player: EntityPlayer, tooltip: util.List[String], advanced: Boolean) {
tooltip.addAll(Tooltip.get(unlocalizedName))
}
override def icon(side: ForgeDirection) = Some(icon)
override def registerIcons(iconRegister: IconRegister) = {
icon = iconRegister.registerIcon(Settings.resourceDomain + ":charger")
}
override def hasTileEntity = true
override def createTileEntity(world: World) = Some(new tileentity.Charger())
override def onNeighborBlockChange(world: World, x: Int, y: Int, z: Int, blockId: Int) =
world.getBlockTileEntity(x, y, z) match {
case charger: tileentity.Charger => charger.onNeighborChanged()
case _ =>
}
}

View File

@ -0,0 +1,53 @@
package li.cil.oc.common.tileentity
import li.cil.oc.api.network.{Node, Visibility}
import li.cil.oc.client.{PacketSender => ClientPacketSender}
import li.cil.oc.{Settings, api}
import net.minecraftforge.common.ForgeDirection
class Charger extends Environment with Redstone {
val node = api.Network.newNode(this, Visibility.None).
withConnector().
create()
private val robots = Array.fill(6)(None: Option[RobotProxy])
private var chargeSpeed = 0.0
override def updateEntity() {
super.updateEntity()
updateRedstoneInput()
if (chargeSpeed > 0) {
val charge = Settings.get.chargeRate * chargeSpeed
robots.collect {
case Some(proxy) => node.changeBuffer(proxy.robot.battery.changeBuffer(charge + node.changeBuffer(-charge)))
}
}
}
override def validate() {
super.validate()
if (isClient) {
ClientPacketSender.sendRedstoneStateRequest(this)
}
}
override def onConnect(node: Node) {
super.onConnect(node)
if (node == this.node) {
onNeighborChanged()
}
}
override protected def onRedstoneInputChanged(side: ForgeDirection) {
super.onRedstoneInputChanged(side)
chargeSpeed = 0.0 max (ForgeDirection.VALID_DIRECTIONS.map(input).max min 15) / 15.0
}
def onNeighborChanged() {
ForgeDirection.VALID_DIRECTIONS.map(side => (side.ordinal(), world.getBlockTileEntity(x + side.offsetX, y + side.offsetY, z + side.offsetZ))).collect {
case (side, proxy: RobotProxy) => robots(side) = Some(proxy)
}
}
}

View File

@ -117,10 +117,8 @@ abstract class Computer(isRemote: Boolean) extends Environment with ComponentInv
override protected def onRedstoneInputChanged(side: ForgeDirection) { override protected def onRedstoneInputChanged(side: ForgeDirection) {
super.onRedstoneInputChanged(side) super.onRedstoneInputChanged(side)
if (isServer) {
computer.signal("redstone_changed", Int.box(side.ordinal())) computer.signal("redstone_changed", Int.box(side.ordinal()))
} }
}
// ----------------------------------------------------------------------- // // ----------------------------------------------------------------------- //

View File

@ -51,7 +51,7 @@ trait Redstone extends TileEntity with network.Environment with Rotatable with P
} }
def checkRedstoneInputChanged() { def checkRedstoneInputChanged() {
shouldUpdateInput = true shouldUpdateInput = isServer
} }
// ----------------------------------------------------------------------- // // ----------------------------------------------------------------------- //

View File

@ -206,8 +206,8 @@ class Robot(isRemote: Boolean) extends Computer(isRemote) with ISidedInventory w
if (isServer) { if (isServer) {
if (computer.isRunning && !computer.isPaused) { if (computer.isRunning && !computer.isPaused) {
// TODO just for testing... until we have charging stations // TODO just for testing... until we have charging stations
distributor.changeBuffer(Settings.get.robotCost + 0.1) battery.changeBuffer(Settings.get.robotCost + 0.1)
distributor.changeBuffer(Settings.get.computerCost - Settings.get.robotCost) battery.changeBuffer(Settings.get.computerCost - Settings.get.robotCost)
} }
distributor.update() distributor.update()
gpu.update() gpu.update()
@ -286,7 +286,6 @@ class Robot(isRemote: Boolean) extends Computer(isRemote) with ISidedInventory w
computer.node.connect(gpu.node) computer.node.connect(gpu.node)
distributor.node.connect(battery) distributor.node.connect(battery)
buffer.node.connect(keyboard.node) buffer.node.connect(keyboard.node)
distributor.changeBuffer(distributor.globalBufferSize / 2) // TODO for testing only
} }
} }

View File

@ -76,7 +76,8 @@ class Screen(var tier: Int) extends Buffer with SidedEnvironment with Rotatable
litPixels = buffer.lines.foldLeft(0)((acc, line) => acc + line.count(_ != ' ')) litPixels = buffer.lines.foldLeft(0)((acc, line) => acc + line.count(_ != ' '))
} }
val hadPower = hasPower val hadPower = hasPower
hasPower = buffer.node.changeBuffer(-(Settings.get.screenCost + pixelCost * litPixels)) val neededPower = Settings.get.screenCost + pixelCost * litPixels
hasPower = buffer.node.tryChangeBuffer(-neededPower)
if (hasPower != hadPower) { if (hasPower != hadPower) {
ServerPacketSender.sendScreenPowerChange(this, hasPower) ServerPacketSender.sendScreenPowerChange(this, hasPower)
} }

View File

@ -220,11 +220,11 @@ class Computer(val owner: tileentity.Computer) extends ManagedComponent with Con
Computer.State.Stopping | Computer.State.Stopping |
Computer.State.Stopped => // No power consumption. Computer.State.Stopped => // No power consumption.
case Computer.State.Sleeping if lastUpdate < sleepUntil && signals.isEmpty => case Computer.State.Sleeping if lastUpdate < sleepUntil && signals.isEmpty =>
if (!node.changeBuffer(-Settings.get.computerCost * Settings.get.sleepCostFactor)) { if (!node.tryChangeBuffer(-Settings.get.computerCost * Settings.get.sleepCostFactor)) {
crash("not enough energy") crash("not enough energy")
} }
case _ => case _ =>
if (!node.changeBuffer(-Settings.get.computerCost)) { if (!node.tryChangeBuffer(-Settings.get.computerCost)) {
crash("not enough energy") crash("not enough energy")
} }
}) })
@ -494,6 +494,9 @@ class Computer(val owner: tileentity.Computer) extends ManagedComponent with Con
override def save(nbt: NBTTagCompound): Unit = this.synchronized { override def save(nbt: NBTTagCompound): Unit = this.synchronized {
assert(state.top != Computer.State.Running) // Lock on 'this' should guarantee this. assert(state.top != Computer.State.Running) // Lock on 'this' should guarantee this.
// Make sure we don't continue running until everything has saved.
pause(0.05)
super.save(nbt) super.save(nbt)
// Make sure the component list is up-to-date. // Make sure the component list is up-to-date.

View File

@ -144,7 +144,7 @@ class FileSystem(val fileSystem: api.fs.FileSystem, var label: Label) extends Ma
Array.copy(buffer, 0, bytes, 0, read) Array.copy(buffer, 0, bytes, 0, read)
bytes bytes
} }
if (!node.changeBuffer(-Settings.get.hddReadCost * bytes.length)) { if (!node.tryChangeBuffer(-Settings.get.hddReadCost * bytes.length)) {
throw new IOException("not enough energy") throw new IOException("not enough energy")
} }
result(bytes) result(bytes)
@ -179,7 +179,7 @@ class FileSystem(val fileSystem: api.fs.FileSystem, var label: Label) extends Ma
def write(context: Context, args: Arguments): Array[AnyRef] = { def write(context: Context, args: Arguments): Array[AnyRef] = {
val handle = args.checkInteger(0) val handle = args.checkInteger(0)
val value = args.checkByteArray(1) val value = args.checkByteArray(1)
if (!node.changeBuffer(-Settings.get.hddWriteCost * value.length)) { if (!node.tryChangeBuffer(-Settings.get.hddWriteCost * value.length)) {
throw new IOException("not enough energy") throw new IOException("not enough energy")
} }
checkOwner(context.address, handle) checkOwner(context.address, handle)

View File

@ -182,12 +182,14 @@ abstract class GraphicsCard extends ManagedComponent {
s.fill(x, y, w, h, value.charAt(0)) s.fill(x, y, w, h, value.charAt(0))
result(true) result(true)
} }
else result(false) else {
result(false)
}
}) })
else throw new Exception("invalid fill value") else throw new Exception("invalid fill value")
} }
private def consumePower(n: Double, cost: Double) = node.changeBuffer(-n * cost) private def consumePower(n: Double, cost: Double) = node.tryChangeBuffer(-n * cost)
// ----------------------------------------------------------------------- // // ----------------------------------------------------------------------- //

View File

@ -36,20 +36,29 @@ class PowerDistributor(val owner: PowerInformation) extends ManagedComponent {
// ----------------------------------------------------------------------- // // ----------------------------------------------------------------------- //
def canChangeBuffer(delta: Double) = { def changeBuffer(delta: Double): Double = {
Settings.get.ignorePower || globalBuffer + delta >= 0 if (delta == 0) {
return 0
} }
if (Settings.get.ignorePower) {
def changeBuffer(delta: Double): Boolean = { if (delta < 0) {
if (delta != 0) this.synchronized { return 0
}
else /* if (delta > 0) */ {
return delta
}
}
this.synchronized {
val oldBuffer = globalBuffer val oldBuffer = globalBuffer
globalBuffer = (globalBuffer + delta) max 0 min globalBufferSize globalBuffer = (globalBuffer + delta) max 0 min globalBufferSize
if (globalBuffer != oldBuffer) { if (globalBuffer == oldBuffer) {
return delta
}
dirty = true dirty = true
if (delta < 0) { if (delta < 0) {
var remaining = -delta var remaining = -delta
for (connector <- buffers) { for (connector <- buffers) {
connector.synchronized(if (connector.localBuffer > 0) { if (connector.localBuffer > 0) {
connector.dirty = true connector.dirty = true
if (connector.localBuffer < remaining) { if (connector.localBuffer < remaining) {
remaining -= connector.localBuffer remaining -= connector.localBuffer
@ -57,15 +66,16 @@ class PowerDistributor(val owner: PowerInformation) extends ManagedComponent {
} }
else { else {
connector.localBuffer -= remaining connector.localBuffer -= remaining
return true return 0
}
})
} }
} }
else if (delta > 0) { }
remaining
}
else /* if (delta > 0) */ {
var remaining = delta var remaining = delta
for (connector <- buffers) { for (connector <- buffers) {
connector.synchronized(if (connector.localBuffer < connector.localBufferSize) { if (connector.localBuffer < connector.localBufferSize) {
connector.dirty = true connector.dirty = true
val space = connector.localBufferSize - connector.localBuffer val space = connector.localBufferSize - connector.localBuffer
if (space < remaining) { if (space < remaining) {
@ -74,14 +84,13 @@ class PowerDistributor(val owner: PowerInformation) extends ManagedComponent {
} }
else { else {
connector.localBuffer += remaining connector.localBuffer += remaining
return true return 0
}
})
} }
} }
} }
remaining
}
} }
false
} }
// ----------------------------------------------------------------------- // // ----------------------------------------------------------------------- //

View File

@ -384,15 +384,15 @@ class Robot(val robot: tileentity.Robot) extends Computer(robot) {
result(false, what) result(false, what)
} }
else { else {
if (!robot.distributor.canChangeBuffer(-Settings.get.robotMoveCost)) { if (!robot.battery.tryChangeBuffer(-Settings.get.robotMoveCost)) {
result(false, "not enough energy") result(false, "not enough energy")
} }
else if (robot.move(direction)) { else if (robot.move(direction)) {
context.pause(Settings.get.moveDelay) context.pause(Settings.get.moveDelay)
robot.distributor.changeBuffer(-Settings.get.robotMoveCost)
result(true) result(true)
} }
else { else {
robot.battery.changeBuffer(Settings.get.robotMoveCost)
result(false, "impossible move") result(false, "impossible move")
} }
} }
@ -401,7 +401,7 @@ class Robot(val robot: tileentity.Robot) extends Computer(robot) {
@LuaCallback("turn") @LuaCallback("turn")
def turn(context: Context, args: Arguments): Array[AnyRef] = { def turn(context: Context, args: Arguments): Array[AnyRef] = {
val clockwise = args.checkBoolean(0) val clockwise = args.checkBoolean(0)
if (robot.distributor.changeBuffer(-Settings.get.robotTurnCost)) { if (robot.battery.tryChangeBuffer(-Settings.get.robotTurnCost)) {
if (clockwise) robot.rotate(ForgeDirection.UP) if (clockwise) robot.rotate(ForgeDirection.UP)
else robot.rotate(ForgeDirection.DOWN) else robot.rotate(ForgeDirection.DOWN)
robot.animateTurn(clockwise, Settings.get.turnDelay) robot.animateTurn(clockwise, Settings.get.turnDelay)

View File

@ -103,7 +103,7 @@ class WirelessNetworkCard(val owner: TileEntity) extends NetworkCard {
private def checkPower() { private def checkPower() {
val cost = Settings.get.wirelessCostPerRange val cost = Settings.get.wirelessCostPerRange
if (cost > 0 && !Settings.get.ignorePower) { if (cost > 0 && !Settings.get.ignorePower) {
if (node.globalBuffer < cost || !node.changeBuffer(-strength * cost)) { if (!node.tryChangeBuffer(-strength * cost)) {
throw new IOException("not enough energy") throw new IOException("not enough energy")
} }
} }

View File

@ -289,7 +289,7 @@ class Player(val robot: Robot) extends EntityPlayer(robot.world, Settings.get.na
override def addExhaustion(amount: Float) { override def addExhaustion(amount: Float) {
if (Settings.get.robotExhaustionCost > 0) { if (Settings.get.robotExhaustionCost > 0) {
robot.distributor.changeBuffer(-Settings.get.robotExhaustionCost * amount) robot.battery.changeBuffer(-Settings.get.robotExhaustionCost * amount)
} }
} }

View File

@ -25,10 +25,21 @@ trait Connector extends Node with network.Connector with Persistable {
// ----------------------------------------------------------------------- // // ----------------------------------------------------------------------- //
def changeBuffer(delta: Double) = if (delta != 0) { def changeBuffer(delta: Double): Double = {
val remaining = this.synchronized { if (delta == 0) {
return 0
}
if (Settings.get.ignorePower) {
if (delta < 0) {
return 0
}
else /* if (delta > 0) */ {
return delta
}
}
def change() = {
val oldBuffer = localBuffer val oldBuffer = localBuffer
localBuffer = localBuffer + delta localBuffer += delta
val remaining = if (localBuffer < 0) { val remaining = if (localBuffer < 0) {
val remaining = localBuffer val remaining = localBuffer
localBuffer = 0 localBuffer = 0
@ -43,17 +54,53 @@ trait Connector extends Node with network.Connector with Persistable {
dirty ||= (localBuffer != oldBuffer) dirty ||= (localBuffer != oldBuffer)
remaining remaining
} }
distributor.fold(remaining == 0)(_.changeBuffer(remaining)) || Settings.get.ignorePower this.synchronized(distributor match {
} else true case Some(d) => d.synchronized(d.changeBuffer(change()))
case _ => change()
})
}
def tryChangeBuffer(delta: Double): Boolean = {
if (delta == 0) {
return true
}
if (Settings.get.ignorePower) {
if (delta < 0) {
return true
}
else /* if (delta > 0) */ {
return false
}
}
this.synchronized(distributor match {
case Some(d) => d.synchronized {
val newGlobalBuffer = globalBuffer + delta
newGlobalBuffer >= 0 && newGlobalBuffer <= globalBufferSize && d.changeBuffer(delta) == 0
}
case _ =>
val newLocalBuffer = localBuffer + delta
if (newLocalBuffer < 0 || newLocalBuffer > localBufferSize) {
false
}
else {
localBuffer = newLocalBuffer
true
}
})
}
def setLocalBufferSize(size: Double) { def setLocalBufferSize(size: Double) {
val remaining = this.synchronized { this.synchronized(distributor match {
case Some(d) => d.synchronized {
localBufferSize = size max 0 localBufferSize = size max 0
val surplus = (localBuffer - localBufferSize) max 0 val surplus = (localBuffer - localBufferSize) max 0
localBuffer = localBuffer min localBufferSize localBuffer = localBuffer min localBufferSize
surplus d.changeBuffer(surplus)
} }
distributor.foreach(_.changeBuffer(remaining)) case _ =>
localBufferSize = size max 0
localBuffer = localBuffer min localBufferSize
})
} }
// ----------------------------------------------------------------------- // // ----------------------------------------------------------------------- //
@ -64,8 +111,7 @@ trait Connector extends Node with network.Connector with Persistable {
} }
else if (distributor.isEmpty) { else if (distributor.isEmpty) {
node.host match { node.host match {
case distributor: PowerDistributor => case d: PowerDistributor => this.synchronized(distributor = Some(d))
this.distributor = Some(distributor)
case _ => case _ =>
} }
} }
@ -73,7 +119,7 @@ trait Connector extends Node with network.Connector with Persistable {
} }
override def onDisconnect(node: ImmutableNode) { override def onDisconnect(node: ImmutableNode) {
if (node == this) { if (node == this) this.synchronized {
setLocalBufferSize(0) setLocalBufferSize(0)
distributor = None distributor = None
} }
@ -84,7 +130,7 @@ trait Connector extends Node with network.Connector with Persistable {
} }
private def findDistributor() = { private def findDistributor() = {
distributor = reachableNodes.find(_.host.isInstanceOf[PowerDistributor]).fold(None: Option[PowerDistributor])(n => Some(n.host.asInstanceOf[PowerDistributor])) this.synchronized(distributor = reachableNodes.find(_.host.isInstanceOf[PowerDistributor]).fold(None: Option[PowerDistributor])(n => Some(n.host.asInstanceOf[PowerDistributor])))
} }
// ----------------------------------------------------------------------- // // ----------------------------------------------------------------------- //

View File

@ -240,6 +240,13 @@ opencomputers {
# internal energy units one Joule generates. # internal energy units one Joule generates.
ratioUniversalElectricity: 5.0 ratioUniversalElectricity: 5.0
# The amount of energy a Charge transfers to each adjacent robot per tick
# if a maximum strength redstone signal is set. Chargers load robots with
# a controllable speed, based on the maximum strength of redstone signals
# going into the block. So if a redstone signal of eight is set, it'll
# charge robots at roughly half speed.
chargerChargeRate: 2500.0
buffer { buffer {
# The amount of energy a single capacitor can store. # The amount of energy a single capacitor can store.
capacitor: 8000.0 capacitor: 8000.0