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,9 +117,7 @@ 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,52 +36,61 @@ 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
}
def changeBuffer(delta: Double): Boolean = { if (Settings.get.ignorePower) {
if (delta != 0) this.synchronized { if (delta < 0) {
val oldBuffer = globalBuffer return 0
globalBuffer = (globalBuffer + delta) max 0 min globalBufferSize }
if (globalBuffer != oldBuffer) { else /* if (delta > 0) */ {
dirty = true return delta
if (delta < 0) { }
var remaining = -delta }
for (connector <- buffers) { this.synchronized {
connector.synchronized(if (connector.localBuffer > 0) { val oldBuffer = globalBuffer
connector.dirty = true globalBuffer = (globalBuffer + delta) max 0 min globalBufferSize
if (connector.localBuffer < remaining) { if (globalBuffer == oldBuffer) {
remaining -= connector.localBuffer return delta
connector.localBuffer = 0 }
} dirty = true
else { if (delta < 0) {
connector.localBuffer -= remaining var remaining = -delta
return true for (connector <- buffers) {
} if (connector.localBuffer > 0) {
}) connector.dirty = true
} if (connector.localBuffer < remaining) {
} remaining -= connector.localBuffer
else if (delta > 0) { connector.localBuffer = 0
var remaining = delta }
for (connector <- buffers) { else {
connector.synchronized(if (connector.localBuffer < connector.localBufferSize) { connector.localBuffer -= remaining
connector.dirty = true return 0
val space = connector.localBufferSize - connector.localBuffer }
if (space < remaining) { }
remaining -= space }
connector.localBuffer = connector.localBufferSize remaining
} }
else { else /* if (delta > 0) */ {
connector.localBuffer += remaining var remaining = delta
return true 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
} }
// ----------------------------------------------------------------------- // // ----------------------------------------------------------------------- //

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 {
localBufferSize = size max 0 case Some(d) => d.synchronized {
val surplus = (localBuffer - localBufferSize) max 0 localBufferSize = size max 0
localBuffer = localBuffer min localBufferSize val surplus = (localBuffer - localBufferSize) max 0
surplus localBuffer = localBuffer min localBufferSize
} 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