From c52e18a87b3b38eb33ba1c8741ff567de3321292 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20N=C3=BCcke?= Date: Tue, 26 Nov 2013 14:35:14 +0100 Subject: [PATCH] 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 --- li/cil/oc/Settings.scala | 2 +- li/cil/oc/api/network/Connector.java | 26 +++-- li/cil/oc/common/block/Charger.scala | 37 +++++++ li/cil/oc/common/tileentity/Charger.scala | 53 ++++++++++ li/cil/oc/common/tileentity/Computer.scala | 4 +- li/cil/oc/common/tileentity/Redstone.scala | 2 +- li/cil/oc/common/tileentity/Robot.scala | 5 +- li/cil/oc/common/tileentity/Screen.scala | 3 +- li/cil/oc/server/component/Computer.scala | 7 +- li/cil/oc/server/component/Filesystem.scala | 4 +- li/cil/oc/server/component/GraphicsCard.scala | 6 +- .../server/component/PowerDistributor.scala | 97 ++++++++++--------- li/cil/oc/server/component/Robot.scala | 6 +- .../component/WirelessNetworkCard.scala | 2 +- li/cil/oc/server/component/robot/Player.scala | 2 +- li/cil/oc/server/network/Connector.scala | 78 ++++++++++++--- reference.conf | 7 ++ 17 files changed, 251 insertions(+), 90 deletions(-) create mode 100644 li/cil/oc/common/block/Charger.scala create mode 100644 li/cil/oc/common/tileentity/Charger.scala diff --git a/li/cil/oc/Settings.scala b/li/cil/oc/Settings.scala index e4f95d0e4..933755672 100644 --- a/li/cil/oc/Settings.scala +++ b/li/cil/oc/Settings.scala @@ -71,11 +71,11 @@ class Settings(config: Config) { val ratioBuildCraft = config.getDouble("power.ratioBuildCraft").toFloat val ratioIndustrialCraft2 = config.getDouble("power.ratioIndustrialCraft2").toFloat val ratioUniversalElectricity = config.getDouble("power.ratioUniversalElectricity").toFloat + val chargeRate = config.getDouble("power.chargerChargeRate") // power.buffer val bufferCapacitor = config.getDouble("power.buffer.capacitor") max 0 val bufferCapacitorAdjacencyBonus = config.getDouble("power.buffer.capacitorAdjacencyBonus") max 0 - val bufferChargingStation = 50000.0 val bufferRobot = config.getDouble("power.buffer.robot") max 0 // power.cost diff --git a/li/cil/oc/api/network/Connector.java b/li/cil/oc/api/network/Connector.java index b0449f6a2..7189915bd 100644 --- a/li/cil/oc/api/network/Connector.java +++ b/li/cil/oc/api/network/Connector.java @@ -53,24 +53,30 @@ public interface Connector extends Node { * a program tries to display text on it. For running costs just apply the * same delta each tick. *

- * For negative values, if there is not enough energy stored in the buffer - * this will return false, and the operation depending on that - * energy should fail - what energy there is will still be consumed, though! + * If the specified delta cannot be completely applied to the buffer, the + * remaining delta will be returned. This means that for negative values + * a part of the energy will have been consumed, though. *

- * For positive values, if there is a buffer overflow due to the added - * energy the surplus will be lost and this will return false. - *

- * If there is enough energy or no overflow this will return true. + * If there is enough energy or no overflow this will return 0. *

* Keep in mind that this change is applied to the global buffer, * 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 * instance) will be prioritized, though. * - * @param delta the amount of energy to consume or make available. - * @return whether the energy could be consumed or stored. + * @param delta the amount of energy to consume or store. + * @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 true if the energy was successfully consumed or stored. + */ + boolean tryChangeBuffer(double delta); /** * Change the size of the connectors local buffer. diff --git a/li/cil/oc/common/block/Charger.scala b/li/cil/oc/common/block/Charger.scala new file mode 100644 index 000000000..5725bd6e9 --- /dev/null +++ b/li/cil/oc/common/block/Charger.scala @@ -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 _ => + } +} diff --git a/li/cil/oc/common/tileentity/Charger.scala b/li/cil/oc/common/tileentity/Charger.scala new file mode 100644 index 000000000..f58563775 --- /dev/null +++ b/li/cil/oc/common/tileentity/Charger.scala @@ -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) + } + } +} diff --git a/li/cil/oc/common/tileentity/Computer.scala b/li/cil/oc/common/tileentity/Computer.scala index 531498f74..ba4380df7 100644 --- a/li/cil/oc/common/tileentity/Computer.scala +++ b/li/cil/oc/common/tileentity/Computer.scala @@ -117,9 +117,7 @@ abstract class Computer(isRemote: Boolean) extends Environment with ComponentInv override protected def onRedstoneInputChanged(side: ForgeDirection) { super.onRedstoneInputChanged(side) - if (isServer) { - computer.signal("redstone_changed", Int.box(side.ordinal())) - } + computer.signal("redstone_changed", Int.box(side.ordinal())) } // ----------------------------------------------------------------------- // diff --git a/li/cil/oc/common/tileentity/Redstone.scala b/li/cil/oc/common/tileentity/Redstone.scala index 61480add0..db782ceeb 100644 --- a/li/cil/oc/common/tileentity/Redstone.scala +++ b/li/cil/oc/common/tileentity/Redstone.scala @@ -51,7 +51,7 @@ trait Redstone extends TileEntity with network.Environment with Rotatable with P } def checkRedstoneInputChanged() { - shouldUpdateInput = true + shouldUpdateInput = isServer } // ----------------------------------------------------------------------- // diff --git a/li/cil/oc/common/tileentity/Robot.scala b/li/cil/oc/common/tileentity/Robot.scala index c547b5fbe..bb35bb435 100644 --- a/li/cil/oc/common/tileentity/Robot.scala +++ b/li/cil/oc/common/tileentity/Robot.scala @@ -206,8 +206,8 @@ class Robot(isRemote: Boolean) extends Computer(isRemote) with ISidedInventory w if (isServer) { if (computer.isRunning && !computer.isPaused) { // TODO just for testing... until we have charging stations - distributor.changeBuffer(Settings.get.robotCost + 0.1) - distributor.changeBuffer(Settings.get.computerCost - Settings.get.robotCost) + battery.changeBuffer(Settings.get.robotCost + 0.1) + battery.changeBuffer(Settings.get.computerCost - Settings.get.robotCost) } distributor.update() gpu.update() @@ -286,7 +286,6 @@ class Robot(isRemote: Boolean) extends Computer(isRemote) with ISidedInventory w computer.node.connect(gpu.node) distributor.node.connect(battery) buffer.node.connect(keyboard.node) - distributor.changeBuffer(distributor.globalBufferSize / 2) // TODO for testing only } } diff --git a/li/cil/oc/common/tileentity/Screen.scala b/li/cil/oc/common/tileentity/Screen.scala index 9f93c0213..0fe1eaaa4 100644 --- a/li/cil/oc/common/tileentity/Screen.scala +++ b/li/cil/oc/common/tileentity/Screen.scala @@ -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(_ != ' ')) } 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) { ServerPacketSender.sendScreenPowerChange(this, hasPower) } diff --git a/li/cil/oc/server/component/Computer.scala b/li/cil/oc/server/component/Computer.scala index 740a750dc..8ce415af5 100644 --- a/li/cil/oc/server/component/Computer.scala +++ b/li/cil/oc/server/component/Computer.scala @@ -220,11 +220,11 @@ class Computer(val owner: tileentity.Computer) extends ManagedComponent with Con Computer.State.Stopping | Computer.State.Stopped => // No power consumption. 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") } case _ => - if (!node.changeBuffer(-Settings.get.computerCost)) { + if (!node.tryChangeBuffer(-Settings.get.computerCost)) { 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 { 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) // Make sure the component list is up-to-date. diff --git a/li/cil/oc/server/component/Filesystem.scala b/li/cil/oc/server/component/Filesystem.scala index 5380bfe54..c0f2c8fb6 100644 --- a/li/cil/oc/server/component/Filesystem.scala +++ b/li/cil/oc/server/component/Filesystem.scala @@ -144,7 +144,7 @@ class FileSystem(val fileSystem: api.fs.FileSystem, var label: Label) extends Ma Array.copy(buffer, 0, bytes, 0, read) bytes } - if (!node.changeBuffer(-Settings.get.hddReadCost * bytes.length)) { + if (!node.tryChangeBuffer(-Settings.get.hddReadCost * bytes.length)) { throw new IOException("not enough energy") } 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] = { val handle = args.checkInteger(0) 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") } checkOwner(context.address, handle) diff --git a/li/cil/oc/server/component/GraphicsCard.scala b/li/cil/oc/server/component/GraphicsCard.scala index 0ccd6de26..55f5ba0c5 100644 --- a/li/cil/oc/server/component/GraphicsCard.scala +++ b/li/cil/oc/server/component/GraphicsCard.scala @@ -182,12 +182,14 @@ abstract class GraphicsCard extends ManagedComponent { s.fill(x, y, w, h, value.charAt(0)) result(true) } - else result(false) + else { + result(false) + } }) 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) // ----------------------------------------------------------------------- // diff --git a/li/cil/oc/server/component/PowerDistributor.scala b/li/cil/oc/server/component/PowerDistributor.scala index 32c452cb5..e6609349f 100644 --- a/li/cil/oc/server/component/PowerDistributor.scala +++ b/li/cil/oc/server/component/PowerDistributor.scala @@ -36,52 +36,61 @@ class PowerDistributor(val owner: PowerInformation) extends ManagedComponent { // ----------------------------------------------------------------------- // - def canChangeBuffer(delta: Double) = { - Settings.get.ignorePower || globalBuffer + delta >= 0 - } - - def changeBuffer(delta: Double): Boolean = { - if (delta != 0) this.synchronized { - val oldBuffer = globalBuffer - globalBuffer = (globalBuffer + delta) max 0 min globalBufferSize - if (globalBuffer != oldBuffer) { - dirty = true - if (delta < 0) { - var remaining = -delta - for (connector <- buffers) { - connector.synchronized(if (connector.localBuffer > 0) { - connector.dirty = true - if (connector.localBuffer < remaining) { - remaining -= connector.localBuffer - connector.localBuffer = 0 - } - else { - connector.localBuffer -= remaining - return true - } - }) - } - } - else if (delta > 0) { - var remaining = delta - for (connector <- buffers) { - connector.synchronized(if (connector.localBuffer < connector.localBufferSize) { - connector.dirty = true - val space = connector.localBufferSize - connector.localBuffer - if (space < remaining) { - remaining -= space - connector.localBuffer = connector.localBufferSize - } - else { - connector.localBuffer += remaining - return true - } - }) - } - } + def changeBuffer(delta: Double): Double = { + if (delta == 0) { + return 0 + } + if (Settings.get.ignorePower) { + if (delta < 0) { + return 0 + } + else /* if (delta > 0) */ { + return delta + } + } + this.synchronized { + val oldBuffer = globalBuffer + globalBuffer = (globalBuffer + delta) max 0 min globalBufferSize + if (globalBuffer == oldBuffer) { + return delta + } + dirty = true + if (delta < 0) { + var remaining = -delta + for (connector <- buffers) { + if (connector.localBuffer > 0) { + connector.dirty = true + if (connector.localBuffer < remaining) { + remaining -= connector.localBuffer + connector.localBuffer = 0 + } + else { + connector.localBuffer -= remaining + return 0 + } + } + } + remaining + } + else /* if (delta > 0) */ { + var remaining = delta + for (connector <- buffers) { + if (connector.localBuffer < connector.localBufferSize) { + connector.dirty = true + val space = connector.localBufferSize - connector.localBuffer + if (space < remaining) { + remaining -= space + connector.localBuffer = connector.localBufferSize + } + else { + connector.localBuffer += remaining + return 0 + } + } + } + remaining } } - false } // ----------------------------------------------------------------------- // diff --git a/li/cil/oc/server/component/Robot.scala b/li/cil/oc/server/component/Robot.scala index c3dfbd59d..ee17de95f 100644 --- a/li/cil/oc/server/component/Robot.scala +++ b/li/cil/oc/server/component/Robot.scala @@ -384,15 +384,15 @@ class Robot(val robot: tileentity.Robot) extends Computer(robot) { result(false, what) } else { - if (!robot.distributor.canChangeBuffer(-Settings.get.robotMoveCost)) { + if (!robot.battery.tryChangeBuffer(-Settings.get.robotMoveCost)) { result(false, "not enough energy") } else if (robot.move(direction)) { context.pause(Settings.get.moveDelay) - robot.distributor.changeBuffer(-Settings.get.robotMoveCost) result(true) } else { + robot.battery.changeBuffer(Settings.get.robotMoveCost) result(false, "impossible move") } } @@ -401,7 +401,7 @@ class Robot(val robot: tileentity.Robot) extends Computer(robot) { @LuaCallback("turn") def turn(context: Context, args: Arguments): Array[AnyRef] = { 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) else robot.rotate(ForgeDirection.DOWN) robot.animateTurn(clockwise, Settings.get.turnDelay) diff --git a/li/cil/oc/server/component/WirelessNetworkCard.scala b/li/cil/oc/server/component/WirelessNetworkCard.scala index 366664907..028115576 100644 --- a/li/cil/oc/server/component/WirelessNetworkCard.scala +++ b/li/cil/oc/server/component/WirelessNetworkCard.scala @@ -103,7 +103,7 @@ class WirelessNetworkCard(val owner: TileEntity) extends NetworkCard { private def checkPower() { val cost = Settings.get.wirelessCostPerRange 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") } } diff --git a/li/cil/oc/server/component/robot/Player.scala b/li/cil/oc/server/component/robot/Player.scala index 7a8058253..834f5374d 100644 --- a/li/cil/oc/server/component/robot/Player.scala +++ b/li/cil/oc/server/component/robot/Player.scala @@ -289,7 +289,7 @@ class Player(val robot: Robot) extends EntityPlayer(robot.world, Settings.get.na override def addExhaustion(amount: Float) { if (Settings.get.robotExhaustionCost > 0) { - robot.distributor.changeBuffer(-Settings.get.robotExhaustionCost * amount) + robot.battery.changeBuffer(-Settings.get.robotExhaustionCost * amount) } } diff --git a/li/cil/oc/server/network/Connector.scala b/li/cil/oc/server/network/Connector.scala index 335e6fe3a..74dece7d5 100644 --- a/li/cil/oc/server/network/Connector.scala +++ b/li/cil/oc/server/network/Connector.scala @@ -25,10 +25,21 @@ trait Connector extends Node with network.Connector with Persistable { // ----------------------------------------------------------------------- // - def changeBuffer(delta: Double) = if (delta != 0) { - val remaining = this.synchronized { + def changeBuffer(delta: Double): Double = { + 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 - localBuffer = localBuffer + delta + localBuffer += delta val remaining = if (localBuffer < 0) { val remaining = localBuffer localBuffer = 0 @@ -43,17 +54,53 @@ trait Connector extends Node with network.Connector with Persistable { dirty ||= (localBuffer != oldBuffer) remaining } - distributor.fold(remaining == 0)(_.changeBuffer(remaining)) || Settings.get.ignorePower - } else true + this.synchronized(distributor match { + 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) { - val remaining = this.synchronized { - localBufferSize = size max 0 - val surplus = (localBuffer - localBufferSize) max 0 - localBuffer = localBuffer min localBufferSize - surplus - } - distributor.foreach(_.changeBuffer(remaining)) + this.synchronized(distributor match { + case Some(d) => d.synchronized { + localBufferSize = size max 0 + val surplus = (localBuffer - localBufferSize) max 0 + localBuffer = localBuffer min localBufferSize + d.changeBuffer(surplus) + } + 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) { node.host match { - case distributor: PowerDistributor => - this.distributor = Some(distributor) + case d: PowerDistributor => this.synchronized(distributor = Some(d)) case _ => } } @@ -73,7 +119,7 @@ trait Connector extends Node with network.Connector with Persistable { } override def onDisconnect(node: ImmutableNode) { - if (node == this) { + if (node == this) this.synchronized { setLocalBufferSize(0) distributor = None } @@ -84,7 +130,7 @@ trait Connector extends Node with network.Connector with Persistable { } 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]))) } // ----------------------------------------------------------------------- // diff --git a/reference.conf b/reference.conf index be83aaaeb..7f11a4c34 100644 --- a/reference.conf +++ b/reference.conf @@ -240,6 +240,13 @@ opencomputers { # internal energy units one Joule generates. 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 { # The amount of energy a single capacitor can store. capacitor: 8000.0