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

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
* same delta each tick.
* <p/>
* For negative values, if there is not enough energy stored in the buffer
* this will return <tt>false</tt>, 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.
* <p/>
* For positive values, if there is a buffer overflow due to the added
* 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>.
* If there is enough energy or no overflow this will return <tt>0</tt>.
* <p/>
* 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
* 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 <tt>true</tt> if the energy was successfully consumed or stored.
*/
boolean tryChangeBuffer(double delta);
/**
* 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) {
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() {
shouldUpdateInput = true
shouldUpdateInput = isServer
}
// ----------------------------------------------------------------------- //

View File

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

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(_ != ' '))
}
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)
}

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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