changed power model to an overflow based one instead of averaging all buffers (buffers across the network will be emptied / filled one after the other, regardless from where the power delta came)

This commit is contained in:
Florian Nücke 2013-11-14 16:22:06 +01:00
parent 0eb6ef41de
commit e68aab67ba
9 changed files with 207 additions and 81 deletions

View File

@ -26,17 +26,27 @@ package li.cil.oc.api.network;
*/ */
public interface Connector extends Node { public interface Connector extends Node {
/** /**
* The size of the buffer. * The power stored in the local buffer.
*/ */
double bufferSize(); double localBuffer();
/** /**
* The power stored in the buffer. * The size of the local buffer.
*/ */
double buffer(); double localBufferSize();
/** /**
* Try to apply the specified delta to the buffer. * The accumulative power stored across all buffers in the node's network.
*/
double globalBuffer();
/**
* The accumulative size of all buffers in the node's network.
*/
double globalBufferSize();
/**
* Try to apply the specified delta to the <em>global</em> buffer.
* <p/> * <p/>
* This can be used to apply reactionary power changes. For example, a * This can be used to apply reactionary power changes. For example, a
* screen may require a certain amount of power to refresh its display when * screen may require a certain amount of power to refresh its display when
@ -45,12 +55,17 @@ public interface Connector extends Node {
* <p/> * <p/>
* For negative values, if there is not enough power stored in the buffer * For negative values, if there is not enough power stored in the buffer
* this will return <tt>false</tt>, and the operation depending on the power * this will return <tt>false</tt>, and the operation depending on the power
* should fail. * should fail - what power there is will still be consumed, though!
* <p/> * <p/>
* For positive values, if there is a buffer overflow due to the added power * For positive values, if there is a buffer overflow due to the added power
* the surplus will be lost and this will return <tt>false</tt>. * the surplus will be lost and this will return <tt>false</tt>.
* <p/> * <p/>
* If there is enough power or no overflow this will return <tt>true</tt>. * If there is enough power or no overflow this will return <tt>true</tt>.
* <p/>
* Keep in mind that this change is applied to the <em>global</em> buffer,
* i.e. power 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 power to consume or make available. * @param delta the amount of power to consume or make available.
* @return whether the power could be consumed or stored. * @return whether the power could be consumed or stored.

View File

@ -28,8 +28,6 @@ class PowerDistributor(val parent: SimpleDelegator) extends SimpleDelegate {
override def getLightValue(world: IBlockAccess, x: Int, y: Int, z: Int) = 5 override def getLightValue(world: IBlockAccess, x: Int, y: Int, z: Int) = 5
// ----------------------------------------------------------------------- //
// Tile entity
// ----------------------------------------------------------------------- // // ----------------------------------------------------------------------- //
override def hasTileEntity = true override def hasTileEntity = true

View File

@ -31,8 +31,8 @@ class Analyzer(val parent: Delegator) extends Delegate {
private def analyzeNode(environment: Environment, player: EntityPlayer) = if (environment != null) { private def analyzeNode(environment: Environment, player: EntityPlayer) = if (environment != null) {
environment.node match { environment.node match {
case connector: Connector => case connector: Connector if connector.localBufferSize > 0 =>
player.addChatMessage("Power: %.2f/%.2f".format(connector.buffer, connector.bufferSize)) player.addChatMessage("Stored power: %.2f/%.2f".format(connector.localBuffer, connector.localBufferSize))
case _ => case _ =>
} }
environment.node match { environment.node match {

View File

@ -19,7 +19,7 @@ class PowerConverter extends Environment with IEnergySink with IPowerReceptor wi
withConnector(Config.bufferConverter). withConnector(Config.bufferConverter).
create() create()
private def demand = node.bufferSize - node.buffer private def demand = node.localBufferSize - node.localBuffer
// ----------------------------------------------------------------------- // // ----------------------------------------------------------------------- //
// Energy conversion ratios, Mode -> Internal // Energy conversion ratios, Mode -> Internal
@ -108,7 +108,7 @@ class PowerConverter extends Environment with IEnergySink with IPowerReceptor wi
// We try to avoid requesting energy when we need less than what we get with // We try to avoid requesting energy when we need less than what we get with
// a single packet. However, if our buffer gets dangerously low we will ask // a single packet. However, if our buffer gets dangerously low we will ask
// for energy even if there's the danger of wasting some energy. // for energy even if there's the danger of wasting some energy.
if (demand >= lastPacketSize * ratioIndustrialCraft || demand > node.bufferSize * 0.5) { if (demand >= lastPacketSize * ratioIndustrialCraft || demand > node.localBufferSize * 0.5) {
demand demand
} else 0 } else 0
} }
@ -130,7 +130,7 @@ class PowerConverter extends Environment with IEnergySink with IPowerReceptor wi
if (node != null && powerHandler.isEmpty) { if (node != null && powerHandler.isEmpty) {
val handler = new PowerHandler(this, PowerHandler.Type.STORAGE) val handler = new PowerHandler(this, PowerHandler.Type.STORAGE)
if (handler != null) { if (handler != null) {
handler.configure(1, 320, Float.MaxValue, node.bufferSize.toFloat / ratioBuildCraft) handler.configure(1, 320, Float.MaxValue, node.localBufferSize.toFloat / ratioBuildCraft)
handler.configurePowerPerdition(0, 0) handler.configurePowerPerdition(0, 0)
powerHandler = Some(handler) powerHandler = Some(handler)
} }

View File

@ -5,26 +5,81 @@ import li.cil.oc.api.network._
import li.cil.oc.client.{PacketSender => ClientPacketSender} import li.cil.oc.client.{PacketSender => ClientPacketSender}
import li.cil.oc.server.network.Connector import li.cil.oc.server.network.Connector
import li.cil.oc.server.{PacketSender => ServerPacketSender} import li.cil.oc.server.{PacketSender => ServerPacketSender}
import net.minecraft.entity.player.EntityPlayer
import scala.collection.convert.WrapAsScala._ import scala.collection.convert.WrapAsScala._
import scala.collection.mutable import scala.collection.mutable
class PowerDistributor extends Environment { class PowerDistributor extends Environment with Analyzable {
val node = api.Network.newNode(this, Visibility.Network).create() val node = api.Network.newNode(this, Visibility.Network).create()
val connectors = mutable.Set.empty[Connector] var globalBuffer = 0.0
var globalBufferSize = 0.0
var average = 0.0 var average = 0.0
private var lastSentAverage = 0.0 private var lastSentAverage = 0.0
private val buffers = mutable.Set.empty[Connector]
private val distributors = mutable.Set.empty[PowerDistributor] private val distributors = mutable.Set.empty[PowerDistributor]
private var dirty = true
// ----------------------------------------------------------------------- //
def onAnalyze(player: EntityPlayer, side: Int, hitX: Float, hitY: Float, hitZ: Float) = {
player.addChatMessage("Global power: %.2f/%.2f".format(globalBuffer, globalBufferSize))
this
}
// ----------------------------------------------------------------------- //
def changeBuffer(delta: Double): Boolean = {
if (delta != 0) {
val oldBuffer = globalBuffer
globalBuffer = (globalBuffer + delta) max 0 min globalBufferSize
if (globalBuffer != oldBuffer) {
dirty = true
if (delta < 0) {
var remaining = -delta
for (connector <- buffers if connector.localBuffer > 0) {
if (connector.localBuffer < remaining) {
remaining -= connector.localBuffer
connector.localBuffer = 0
}
else {
connector.changeBuffer(-remaining)
return true
}
}
}
else if (delta > 0) {
var remaining = delta
for (connector <- buffers if connector.localBuffer < connector.localBufferSize) {
val space = connector.localBufferSize - connector.localBuffer
if (space < remaining) {
remaining -= space
connector.localBuffer = connector.localBufferSize
}
else {
connector.changeBuffer(remaining)
return true
}
}
}
}
}
false
}
// ----------------------------------------------------------------------- // // ----------------------------------------------------------------------- //
override def updateEntity() { override def updateEntity() {
super.updateEntity() super.updateEntity()
if (!worldObj.isRemote && connectors.exists(_.dirty)) if (!worldObj.isRemote && (dirty || buffers.exists(_.dirty))) {
balance() updateCachedValues()
}
} }
override def validate() { override def validate() {
@ -36,38 +91,27 @@ class PowerDistributor extends Environment {
// ----------------------------------------------------------------------- // // ----------------------------------------------------------------------- //
override def onDisconnect(node: Node) {
super.onDisconnect(node)
if (node == this.node) {
connectors.clear()
distributors.clear()
average = -1
}
else node match {
case connector: Connector =>
connectors -= connector
balance()
case _ => node.host match {
case distributor: PowerDistributor => distributors -= distributor
case _ =>
}
}
}
override def onConnect(node: Node) { override def onConnect(node: Node) {
super.onConnect(node) super.onConnect(node)
if (node == this.node) { if (node == this.node) {
for (node <- node.network.nodes) node match { for (node <- node.network.nodes) node match {
case connector: Connector => connectors += connector case connector: Connector if connector.localBufferSize > 0 =>
buffers += connector
globalBuffer += connector.localBuffer
globalBufferSize += connector.localBufferSize
case _ => node.host match { case _ => node.host match {
case distributor: PowerDistributor => distributors += distributor case distributor: PowerDistributor => distributors += distributor
case _ => case _ =>
} }
} }
balance() dirty = true
} }
else node match { else node match {
case connector: Connector => connectors += connector case connector: Connector =>
buffers += connector
globalBuffer += connector.localBuffer
globalBufferSize += connector.localBufferSize
dirty = true
case _ => node.host match { case _ => node.host match {
case distributor: PowerDistributor => distributors += distributor case distributor: PowerDistributor => distributors += distributor
case _ => case _ =>
@ -75,28 +119,48 @@ class PowerDistributor extends Environment {
} }
} }
override def onDisconnect(node: Node) {
super.onDisconnect(node)
if (node == this.node) {
buffers.clear()
distributors.clear()
globalBuffer = 0
globalBufferSize = 0
average = -1
}
else node match {
case connector: Connector =>
buffers -= connector
globalBuffer -= connector.localBuffer
globalBufferSize -= connector.localBufferSize
dirty = true
case _ => node.host match {
case distributor: PowerDistributor => distributors -= distributor
case _ =>
}
}
}
// ----------------------------------------------------------------------- // // ----------------------------------------------------------------------- //
private def balance() { def updateCachedValues() {
// Computer average fill ratio of all buffers. // Computer average fill ratio of all buffers.
val (minRelativeBuffer, maxRelativeBuffer, sumBuffer, sumBufferSize) = val (sumBuffer, sumBufferSize) =
connectors.foldRight((1.0, 0.0, 0.0, 0.0))((c, acc) => { buffers.foldRight((0.0, 0.0))((c, acc) => {
c.dirty = false // clear dirty flag for all connectors c.dirty = false // clear dirty flag for all connectors
(acc._1 min (c.buffer / c.bufferSize), acc._2 max (c.buffer / c.bufferSize), (acc._1 + c.localBuffer, acc._2 + c.localBufferSize)
acc._3 + c.buffer, acc._4 + c.bufferSize)
}) })
average = if (sumBufferSize > 0) sumBuffer / sumBufferSize else 0 average = if (globalBufferSize > 0) globalBuffer / globalBufferSize else 0
if ((lastSentAverage - average).abs > 0.05) { val shouldSend = (lastSentAverage - average).abs > 0.05
lastSentAverage = average for (distributor <- distributors) {
for (distributor <- distributors) { distributor.dirty = false
distributor.average = average distributor.globalBuffer = sumBuffer
distributor.globalBufferSize = sumBufferSize
distributor.average = average
if (shouldSend) {
distributor.lastSentAverage = lastSentAverage distributor.lastSentAverage = lastSentAverage
ServerPacketSender.sendPowerState(distributor) ServerPacketSender.sendPowerState(distributor)
} }
} }
if (maxRelativeBuffer - minRelativeBuffer > 10e-4) {
// Adjust buffer fill ratio for all buffers to average.
connectors.foreach(c => c.buffer = c.bufferSize * average)
}
} }
} }

View File

@ -14,9 +14,9 @@ class PowerSupply extends ManagedComponent {
node.changeBuffer(1) node.changeBuffer(1)
} }
@LuaCallback(value = "bufferSize", direct = true) @LuaCallback(value = "localBufferSize", direct = true)
def bufferSize(context: Context, args: Arguments): Array[AnyRef] = result(node.bufferSize) def bufferSize(context: Context, args: Arguments): Array[AnyRef] = result(node.localBufferSize)
@LuaCallback(value = "buffer", direct = true) @LuaCallback(value = "localBuffer", direct = true)
def buffer(context: Context, args: Arguments): Array[AnyRef] = result(node.buffer) def buffer(context: Context, args: Arguments): Array[AnyRef] = result(node.localBuffer)
} }

View File

@ -2,40 +2,77 @@ package li.cil.oc.server.network
import li.cil.oc.Config import li.cil.oc.Config
import li.cil.oc.api.network import li.cil.oc.api.network
import li.cil.oc.api.network.{Node => ImmutableNode}
import li.cil.oc.common.tileentity.PowerDistributor
import li.cil.oc.util.Persistable import li.cil.oc.util.Persistable
import net.minecraft.nbt.NBTTagCompound import net.minecraft.nbt.NBTTagCompound
import scala.collection.convert.WrapAsScala._
trait Connector extends network.Connector with Persistable { trait Connector extends Node with network.Connector with Persistable {
val bufferSize: Double val localBufferSize: Double
var localBuffer = 0.0
var dirty = true var dirty = true
var buffer = 0.0 private var distributor: Option[PowerDistributor] = None
// ----------------------------------------------------------------------- //
def globalBuffer = distributor.fold(localBuffer)(_.globalBuffer)
def globalBufferSize = distributor.fold(localBufferSize)(_.globalBufferSize)
// ----------------------------------------------------------------------- //
def changeBuffer(delta: Double) = if (delta != 0) { def changeBuffer(delta: Double) = if (delta != 0) {
val oldBuffer = buffer val oldBuffer = localBuffer
buffer = buffer + delta localBuffer = localBuffer + delta
val ok = if (buffer < 0) { val ok = if (localBuffer < 0) {
buffer = 0 val remaining = localBuffer
false localBuffer = 0
distributor.fold(false)(_.changeBuffer(remaining))
} }
else if (buffer > bufferSize) { else if (localBuffer > localBufferSize) {
buffer = bufferSize val remaining = localBuffer - localBufferSize
false localBuffer = localBufferSize
distributor.fold(false)(_.changeBuffer(remaining))
} }
else true else true
if (buffer != oldBuffer) dirty = true dirty ||= (localBuffer != oldBuffer)
ok || Config.ignorePower ok || Config.ignorePower
} else true } else true
// ----------------------------------------------------------------------- //
override def onConnect(node: ImmutableNode) {
if (node == this) findDistributor()
else if (distributor.isEmpty) node.host match {
case distributor: PowerDistributor => this.distributor = Some(distributor)
case _ =>
}
super.onConnect(node)
}
override def onDisconnect(node: ImmutableNode) {
if (node != this && distributor.exists(_ == node.host)) findDistributor()
super.onDisconnect(node)
}
private def findDistributor() {
distributor = reachableNodes.find(_.host.isInstanceOf[PowerDistributor]).fold(None: Option[PowerDistributor])(n => Some(n.host.asInstanceOf[PowerDistributor]))
}
// ----------------------------------------------------------------------- //
override def load(nbt: NBTTagCompound) { override def load(nbt: NBTTagCompound) {
super.load(nbt) super.load(nbt)
buffer = nbt.getDouble(Config.namespace + "connector.buffer") max 0 min bufferSize localBuffer = nbt.getDouble(Config.namespace + "connector.buffer") max 0 min localBufferSize
dirty = true dirty = true
} }
override def save(nbt: NBTTagCompound) { override def save(nbt: NBTTagCompound) {
super.save(nbt) super.save(nbt)
nbt.setDouble(Config.namespace + "connector.buffer", buffer) nbt.setDouble(Config.namespace + "connector.buffer", localBuffer)
} }
} }

View File

@ -16,7 +16,7 @@ class Network private(private val data: mutable.Map[String, Network.Vertex] = mu
def this(node: MutableNode) = { def this(node: MutableNode) = {
this() this()
addNew(node) addNew(node)
node.host.onConnect(node) node.onConnect(node)
} }
private lazy val wrapper = new Network.Wrapper(this) private lazy val wrapper = new Network.Wrapper(this)
@ -48,9 +48,9 @@ class Network private(private val data: mutable.Map[String, Network.Vertex] = mu
assert(!oldNodeB.edges.exists(_.isBetween(oldNodeA, oldNodeB))) assert(!oldNodeB.edges.exists(_.isBetween(oldNodeA, oldNodeB)))
Network.Edge(oldNodeA, oldNodeB) Network.Edge(oldNodeA, oldNodeB)
if (oldNodeA.data.reachability == Visibility.Neighbors) if (oldNodeA.data.reachability == Visibility.Neighbors)
oldNodeB.data.host.onConnect(oldNodeA.data) oldNodeB.data.onConnect(oldNodeA.data)
if (oldNodeB.data.reachability == Visibility.Neighbors) if (oldNodeB.data.reachability == Visibility.Neighbors)
oldNodeA.data.host.onConnect(oldNodeB.data) oldNodeA.data.onConnect(oldNodeB.data)
true true
} }
else false // That connection already exists. else false // That connection already exists.
@ -76,9 +76,9 @@ class Network private(private val data: mutable.Map[String, Network.Vertex] = mu
case Some(edge) => { case Some(edge) => {
handleSplit(edge.remove()) handleSplit(edge.remove())
if (edge.left.data.reachability == Visibility.Neighbors) if (edge.left.data.reachability == Visibility.Neighbors)
edge.right.data.host.onDisconnect(edge.left.data) edge.right.data.onDisconnect(edge.left.data)
if (edge.right.data.reachability == Visibility.Neighbors) if (edge.right.data.reachability == Visibility.Neighbors)
edge.left.data.host.onDisconnect(edge.right.data) edge.left.data.onDisconnect(edge.right.data)
true true
} }
case _ => false // That connection doesn't exists. case _ => false // That connection doesn't exists.
@ -96,7 +96,7 @@ class Network private(private val data: mutable.Map[String, Network.Vertex] = mu
case Visibility.Network => subGraphs.map(_.values.map(_.data)).flatten case Visibility.Network => subGraphs.map(_.values.map(_.data)).flatten
}) })
handleSplit(subGraphs) handleSplit(subGraphs)
targets.foreach(_.host.onDisconnect(node)) targets.foreach(_.asInstanceOf[MutableNode].onDisconnect(node))
true true
} }
case _ => false case _ => false
@ -209,7 +209,7 @@ class Network private(private val data: mutable.Map[String, Network.Vertex] = mu
Network.Edge(oldNode, node(addedNode)) Network.Edge(oldNode, node(addedNode))
} }
for ((node, nodes) <- connects) nodes.foreach(_.host.onConnect(node)) for ((node, nodes) <- connects) nodes.foreach(_.asInstanceOf[MutableNode].onConnect(node))
true true
} }
@ -229,8 +229,8 @@ class Network private(private val data: mutable.Map[String, Network.Vertex] = mu
for (indexB <- (indexA + 1) until subGraphs.length) { for (indexB <- (indexA + 1) until subGraphs.length) {
val nodesB = nodes(indexB) val nodesB = nodes(indexB)
val visibleNodesB = visibleNodes(indexB) val visibleNodesB = visibleNodes(indexB)
visibleNodesA.foreach(node => nodesB.foreach(_.host.onDisconnect(node))) visibleNodesA.foreach(node => nodesB.foreach(_.onDisconnect(node)))
visibleNodesB.foreach(node => nodesA.foreach(_.host.onDisconnect(node))) visibleNodesB.foreach(node => nodesA.foreach(_.onDisconnect(node)))
} }
} }
} }
@ -307,7 +307,7 @@ object Network extends api.detail.NetworkAPI {
def create() = if (FMLCommonHandler.instance.getEffectiveSide == Side.SERVER) new MutableNode with Connector { def create() = if (FMLCommonHandler.instance.getEffectiveSide == Side.SERVER) new MutableNode with Connector {
val host = _host val host = _host
val reachability = _reachability val reachability = _reachability
val bufferSize = _bufferSize val localBufferSize = _bufferSize
} }
else null else null
} }
@ -317,7 +317,7 @@ object Network extends api.detail.NetworkAPI {
val host = _host val host = _host
val reachability = _reachability val reachability = _reachability
val name = _name val name = _name
val bufferSize = _bufferSize val localBufferSize = _bufferSize
setVisibility(_visibility) setVisibility(_visibility)
} }
else null else null

View File

@ -53,13 +53,25 @@ trait Node extends api.network.Node with Persistable {
// ----------------------------------------------------------------------- // // ----------------------------------------------------------------------- //
def onConnect(node: ImmutableNode) {
host.onConnect(node)
}
def onDisconnect(node: ImmutableNode) {
host.onDisconnect(node)
}
// ----------------------------------------------------------------------- //
override def load(nbt: NBTTagCompound) = { override def load(nbt: NBTTagCompound) = {
super.load(nbt)
if (nbt.hasKey(Config.namespace + "node.address")) { if (nbt.hasKey(Config.namespace + "node.address")) {
address = nbt.getString(Config.namespace + "node.address") address = nbt.getString(Config.namespace + "node.address")
} }
} }
override def save(nbt: NBTTagCompound) = { override def save(nbt: NBTTagCompound) = {
super.save(nbt)
if (address != null) { if (address != null) {
nbt.setString(Config.namespace + "node.address", address) nbt.setString(Config.namespace + "node.address", address)
} }