blob: 5189a28cd97672175481c6c9a92a8a6b0763af29 [file] [log] [blame] [raw]
package li.cil.oc.server.network
import cpw.mods.fml.common.FMLCommonHandler
import cpw.mods.fml.relauncher.Side
import li.cil.oc.api.network.{Node => ImmutableNode, SidedEnvironment, Environment, Visibility}
import li.cil.oc.common.tileentity.PassiveNode
import li.cil.oc.server.network.{Node => MutableNode}
import li.cil.oc.{Settings, api}
import net.minecraft.tileentity.TileEntity
import net.minecraftforge.common.ForgeDirection
import net.minecraftforge.event.ForgeSubscribe
import net.minecraftforge.event.world.{ChunkEvent, WorldEvent}
import scala.collection.JavaConverters._
import scala.collection.convert.WrapAsScala._
import scala.collection.mutable
import scala.collection.mutable.ArrayBuffer
// Looking at this again after some time, the similarity to const in C++ is somewhat uncanny.
private class Network private(private val data: mutable.Map[String, Network.Vertex] = mutable.Map.empty) extends Distributor {
def this(node: MutableNode) = {
this()
addNew(node)
node.onConnect(node)
}
var globalBuffer = 0.0
var globalBufferSize = 0.0
private val connectors = mutable.ArrayBuffer.empty[Connector]
private lazy val wrapper = new Network.Wrapper(this)
data.values.foreach(node => {
node.data match {
case connector: Connector => addConnector(connector)
case _ =>
}
node.data.network = wrapper
})
// ----------------------------------------------------------------------- //
def connect(nodeA: MutableNode, nodeB: MutableNode) = {
if (nodeA == nodeB) throw new IllegalArgumentException(
"Cannot connect a node to itself.")
val containsA = contains(nodeA)
val containsB = contains(nodeB)
if (!containsA && !containsB) throw new IllegalArgumentException(
"At least one of the nodes must already be in this network.")
lazy val oldNodeA = node(nodeA)
lazy val oldNodeB = node(nodeB)
if (containsA && containsB) {
// Both nodes already exist in the network but there is a new connection.
// This can happen if a new node sequentially connects to multiple nodes
// in an existing network, e.g. in a setup like so:
// O O Where O is an old node, and N is the new Node. It would connect
// O N to the node above and left to it (in no particular order).
if (!oldNodeA.edges.exists(_.isBetween(oldNodeA, oldNodeB))) {
assert(!oldNodeB.edges.exists(_.isBetween(oldNodeA, oldNodeB)))
Network.Edge(oldNodeA, oldNodeB)
if (oldNodeA.data.reachability == Visibility.Neighbors)
oldNodeB.data.onConnect(oldNodeA.data)
if (oldNodeB.data.reachability == Visibility.Neighbors)
oldNodeA.data.onConnect(oldNodeB.data)
true
}
else false // That connection already exists.
}
else if (containsA) add(oldNodeA, nodeB)
else add(oldNodeB, nodeA)
}
def disconnect(nodeA: MutableNode, nodeB: MutableNode) = {
if (nodeA == nodeB) throw new IllegalArgumentException(
"Cannot disconnect a node from itself.")
val containsA = contains(nodeA)
val containsB = contains(nodeB)
if (!containsA || !containsB) throw new IllegalArgumentException(
"Both of the nodes must be in this network.")
def oldNodeA = node(nodeA)
def oldNodeB = node(nodeB)
oldNodeA.edges.find(_.isBetween(oldNodeA, oldNodeB)) match {
case Some(edge) =>
handleSplit(edge.remove())
if (edge.left.data.reachability == Visibility.Neighbors)
edge.right.data.onDisconnect(edge.left.data)
if (edge.right.data.reachability == Visibility.Neighbors)
edge.left.data.onDisconnect(edge.right.data)
true
case _ => false // That connection doesn't exists.
}
}
def remove(node: MutableNode) = {
data.remove(node.address) match {
case Some(entry) =>
node match {
case connector: Connector => removeConnector(connector)
case _ =>
}
node.network = null
val subGraphs = entry.remove()
val targets = Iterable(node) ++ (entry.data.reachability match {
case Visibility.None => Iterable.empty[ImmutableNode]
case Visibility.Neighbors => entry.edges.map(_.other(entry).data)
case Visibility.Network => subGraphs.map(_.values.map(_.data)).flatten
})
handleSplit(subGraphs)
targets.foreach(_.asInstanceOf[MutableNode].onDisconnect(node))
true
case _ => false
}
}
// ----------------------------------------------------------------------- //
def node(address: String) = {
data.get(address) match {
case Some(node) => node.data
case _ => null
}
}
def nodes: Iterable[ImmutableNode] = data.values.map(_.data)
def nodes(reference: ImmutableNode): Iterable[ImmutableNode] = {
val referenceNeighbors = neighbors(reference).toSet
nodes.filter(node => node != reference && (node.reachability == Visibility.Network ||
(node.reachability == Visibility.Neighbors && referenceNeighbors.contains(node))))
}
def neighbors(node: ImmutableNode): Iterable[ImmutableNode] = {
data.get(node.address) match {
case Some(n) =>
assert(n.data == node)
n.edges.map(_.other(n).data)
case _ => throw new IllegalArgumentException("Node must be in this network.")
}
}
// ----------------------------------------------------------------------- //
def sendToAddress(source: ImmutableNode, target: String, name: String, args: AnyRef*) = {
if (source.network != wrapper)
throw new IllegalArgumentException("Source node must be in this network.")
data.get(target) match {
case Some(node) if node.data.canBeReachedFrom(source) =>
send(source, Iterable(node.data), name, args: _*)
case _ =>
}
}
def sendToNeighbors(source: ImmutableNode, name: String, args: AnyRef*) = {
if (source.network != wrapper)
throw new IllegalArgumentException("Source node must be in this network.")
send(source, neighbors(source).filter(_.reachability != Visibility.None), name, args: _*)
}
def sendToReachable(source: ImmutableNode, name: String, args: AnyRef*) = {
if (source.network != wrapper)
throw new IllegalArgumentException("Source node must be in this network.")
send(source, nodes(source), name, args: _*)
}
def sendToVisible(source: ImmutableNode, name: String, args: AnyRef*) = {
if (source.network != wrapper)
throw new IllegalArgumentException("Source node must be in this network.")
send(source, nodes(source) collect {
case component: api.network.Component if component.canBeSeenFrom(source) => component
}, name, args: _*)
}
// ----------------------------------------------------------------------- //
private def contains(node: ImmutableNode) = data.contains(node.address)
private def node(node: ImmutableNode) = data(node.address)
private def addNew(node: MutableNode) = {
val newNode = new Network.Vertex(node)
if (node.address == null)
node.address = java.util.UUID.randomUUID().toString
data += node.address -> newNode
node match {
case connector: Connector => addConnector(connector)
case _ =>
}
node.network = wrapper
newNode
}
private def add(oldNode: Network.Vertex, addedNode: MutableNode) = {
// Queue onConnect calls to avoid side effects from callbacks.
val connects = mutable.Buffer.empty[(ImmutableNode, Iterable[ImmutableNode])]
// Check if the other node is new or if we have to merge networks.
if (addedNode.network == null) {
val newNode = addNew(addedNode)
Network.Edge(oldNode, newNode)
addedNode.reachability match {
case Visibility.None =>
connects += ((addedNode, Iterable(addedNode)))
case Visibility.Neighbors =>
connects += ((addedNode, Iterable(addedNode) ++ neighbors(addedNode)))
nodes(addedNode).foreach(node => connects += ((node, Iterable(addedNode))))
case Visibility.Network =>
// Explicitly send to the added node itself first.
connects += ((addedNode, Iterable(addedNode) ++ nodes.filter(_ != addedNode)))
nodes(addedNode).foreach(node => connects += ((node, Iterable(addedNode))))
}
}
else {
val otherNetwork = addedNode.network.asInstanceOf[Network.Wrapper].network
if (addedNode.reachability == Visibility.Neighbors)
connects += ((addedNode, Iterable(oldNode.data)))
if (oldNode.data.reachability == Visibility.Neighbors)
connects += ((oldNode.data, Iterable(addedNode)))
val oldNodes = nodes
val newNodes = otherNetwork.nodes
val oldVisibleNodes = oldNodes.filter(_.reachability == Visibility.Network)
val newVisibleNodes = newNodes.filter(_.reachability == Visibility.Network)
newVisibleNodes.foreach(node => connects += ((node, oldNodes)))
oldVisibleNodes.foreach(node => connects += ((node, newNodes)))
data ++= otherNetwork.data
connectors ++= otherNetwork.connectors
globalBuffer += otherNetwork.globalBuffer
globalBufferSize += otherNetwork.globalBufferSize
otherNetwork.data.values.foreach(node => {
node.data match {
case connector: Connector => connector.distributor = Some(wrapper)
case _ =>
}
node.data.network = wrapper
})
Network.Edge(oldNode, node(addedNode))
}
for ((node, nodes) <- connects) nodes.foreach(_.asInstanceOf[MutableNode].onConnect(node))
true
}
private def handleSplit(subGraphs: Seq[mutable.Map[String, Network.Vertex]]) =
if (subGraphs.size > 1) {
val nodes = subGraphs.map(_.values.map(_.data))
val visibleNodes = nodes.map(_.filter(_.reachability == Visibility.Network))
data.clear()
connectors.clear()
globalBuffer = 0
globalBufferSize = 0
data ++= subGraphs.head
for (node <- data.values) node.data match {
case connector: Connector => addConnector(connector)
case _ =>
}
subGraphs.tail.foreach(new Network(_))
for (indexA <- 0 until subGraphs.length) {
val nodesA = nodes(indexA)
val visibleNodesA = visibleNodes(indexA)
for (indexB <- (indexA + 1) until subGraphs.length) {
val nodesB = nodes(indexB)
val visibleNodesB = visibleNodes(indexB)
visibleNodesA.foreach(node => nodesB.foreach(_.onDisconnect(node)))
visibleNodesB.foreach(node => nodesA.foreach(_.onDisconnect(node)))
}
}
}
private def send(source: ImmutableNode, targets: Iterable[ImmutableNode], name: String, args: AnyRef*) {
val message = new Network.Message(source, name, Array(args: _*))
targets.foreach(_.host.onMessage(message))
}
// ----------------------------------------------------------------------- //
def addConnector(connector: Connector) {
if (connector.localBufferSize > 0) {
assert(!connectors.contains(connector))
connectors += connector
globalBuffer += connector.localBuffer
globalBufferSize += connector.localBufferSize
}
connector.distributor = Some(wrapper)
}
def removeConnector(connector: Connector) {
if (connector.localBufferSize > 0) {
assert(connectors.contains(connector))
connectors -= connector
globalBuffer -= connector.localBuffer
globalBufferSize -= connector.localBufferSize
}
}
def changeBuffer(delta: Double): Double = {
if (delta == 0) 0
else if (Settings.get.ignorePower) {
if (delta < 0) 0
else /* if (delta > 0) */ delta
}
else this.synchronized {
val oldBuffer = globalBuffer
globalBuffer = math.min(math.max(globalBuffer + delta, 0), globalBufferSize)
if (globalBuffer == oldBuffer) {
return delta
}
if (delta < 0) {
var remaining = -delta
for (connector <- connectors if remaining > 0) {
if (connector.localBuffer > 0) {
if (connector.localBuffer < remaining) {
remaining -= connector.localBuffer
connector.localBuffer = 0
}
else {
connector.localBuffer -= remaining
remaining = 0
}
}
}
remaining
}
else /* if (delta > 0) */ {
var remaining = delta
for (connector <- connectors if remaining > 0) {
if (connector.localBuffer < connector.localBufferSize) {
val space = connector.localBufferSize - connector.localBuffer
if (space < remaining) {
remaining -= space
connector.localBuffer = connector.localBufferSize
}
else {
connector.localBuffer += remaining
remaining = 0
}
}
}
remaining
}
}
}
}
object Network extends api.detail.NetworkAPI {
@ForgeSubscribe
def onWorldLoad(e: WorldEvent.Load) {
val world = e.world
if (!world.isRemote) {
for (t <- world.loadedTileEntityList) t match {
case p: TileEntity with PassiveNode => p.getBlockType.updateTick(world, p.xCoord, p.yCoord, p.zCoord, world.rand)
case _ =>
}
}
}
@ForgeSubscribe
def onChunkLoad(e: ChunkEvent.Load) {
val world = e.world
if (!world.isRemote) {
for (t <- e.getChunk.chunkTileEntityMap.values) t match {
case p: TileEntity with PassiveNode => p.getBlockType.updateTick(world, p.xCoord, p.yCoord, p.zCoord, world.rand)
case _ =>
}
}
}
override def joinOrCreateNetwork(tileEntity: TileEntity): Unit =
if (!tileEntity.getWorldObj.isRemote) {
for (side <- ForgeDirection.VALID_DIRECTIONS) {
getNetworkNode(tileEntity, side) match {
case Some(node: MutableNode) =>
val (nx, ny, nz) = (
tileEntity.xCoord + side.offsetX,
tileEntity.yCoord + side.offsetY,
tileEntity.zCoord + side.offsetZ)
getNetworkNode(tileEntity.getWorldObj.getBlockTileEntity(nx, ny, nz), side.getOpposite) match {
case Some(neighbor: MutableNode) if neighbor != node && neighbor.network != null => neighbor.connect(node)
case _ => // Ignore.
}
if (node.network == null) {
joinNewNetwork(node)
}
case _ => // No node for this side or bad environment.
}
}
}
def joinNewNetwork(node: ImmutableNode): Unit = node match {
case mutableNode: MutableNode if mutableNode.network == null =>
new Network(mutableNode)
case _ =>
}
private def getNetworkNode(tileEntity: TileEntity, side: ForgeDirection) =
tileEntity match {
case host: SidedEnvironment => Option(host.sidedNode(side))
case host: Environment => Some(host.node)
case _ => None
}
// ----------------------------------------------------------------------- //
def newNode(host: Environment, reachability: Visibility) = new NodeBuilder(host, reachability)
class NodeBuilder(val _host: Environment, val _reachability: Visibility) extends api.detail.Builder.NodeBuilder {
def withComponent(name: String, visibility: Visibility) = new Network.ComponentBuilder(_host, _reachability, name, visibility)
def withComponent(name: String) = withComponent(name, _reachability)
def withConnector(bufferSize: Double) = new Network.ConnectorBuilder(_host, _reachability, bufferSize)
def withConnector() = withConnector(0)
def create() = if (FMLCommonHandler.instance.getEffectiveSide == Side.SERVER) new MutableNode with NodeVarargPart {
val host = _host
val reachability = _reachability
}
else null
}
class ComponentBuilder(val _host: Environment, val _reachability: Visibility, val _name: String, val _visibility: Visibility) extends api.detail.Builder.ComponentBuilder {
def withConnector(bufferSize: Double) = new Network.ComponentConnectorBuilder(_host, _reachability, _name, _visibility, bufferSize)
def withConnector() = withConnector(0)
def create() = if (FMLCommonHandler.instance.getEffectiveSide == Side.SERVER) new Component with NodeVarargPart {
val host = _host
val reachability = _reachability
val name = _name
setVisibility(_visibility)
}
else null
}
class ConnectorBuilder(val _host: Environment, val _reachability: Visibility, val _bufferSize: Double) extends api.detail.Builder.ConnectorBuilder {
def withComponent(name: String, visibility: Visibility) = new Network.ComponentConnectorBuilder(_host, _reachability, name, visibility, _bufferSize)
def withComponent(name: String) = withComponent(name, _reachability)
def create() = if (FMLCommonHandler.instance.getEffectiveSide == Side.SERVER) new Connector with NodeVarargPart {
val host = _host
val reachability = _reachability
var localBufferSize = _bufferSize
}
else null
}
class ComponentConnectorBuilder(val _host: Environment, val _reachability: Visibility, val _name: String, val _visibility: Visibility, val _bufferSize: Double) extends api.detail.Builder.ComponentConnectorBuilder {
def create() = if (FMLCommonHandler.instance.getEffectiveSide == Side.SERVER) new ComponentConnector with NodeVarargPart {
val host = _host
val reachability = _reachability
val name = _name
var localBufferSize = _bufferSize
setVisibility(_visibility)
}
else null
}
// ----------------------------------------------------------------------- //
private class Vertex(val data: MutableNode) {
val edges = ArrayBuffer.empty[Edge]
def remove() = {
edges.foreach(edge => edge.other(this).edges -= edge)
searchGraphs(edges.map(_.other(this)))
}
}
private case class Edge(left: Vertex, right: Vertex) {
left.edges += this
right.edges += this
def other(side: Vertex) = if (side == left) right else left
def isBetween(a: Vertex, b: Vertex) = (a == left && b == right) || (b == left && a == right)
def remove() = {
left.edges -= this
right.edges -= this
searchGraphs(List(left, right))
}
}
private def searchGraphs(seeds: Seq[Vertex]) = {
val seen = mutable.Set.empty[Vertex]
seeds.map(seed => {
if (seen.contains(seed)) None
else {
val addressed = mutable.Map.empty[String, Vertex]
val queue = mutable.Queue(seed)
while (queue.nonEmpty) {
val node = queue.dequeue()
seen += node
addressed += node.data.address -> node
queue ++= node.edges.map(_.other(node)).filter(n => !seen.contains(n) && !queue.contains(n))
}
Some(addressed)
}
}) filter (_.nonEmpty) map (_.get)
}
// ----------------------------------------------------------------------- //
private class Message(val source: ImmutableNode, val name: String, val data: Array[AnyRef]) extends api.network.Message {
var isCanceled = false
def cancel() = isCanceled = true
}
// ----------------------------------------------------------------------- //
private class Wrapper(val network: Network) extends api.network.Network with Distributor {
def connect(nodeA: ImmutableNode, nodeB: ImmutableNode) =
network.connect(nodeA.asInstanceOf[MutableNode], nodeB.asInstanceOf[MutableNode])
def disconnect(nodeA: ImmutableNode, nodeB: ImmutableNode) =
network.disconnect(nodeA.asInstanceOf[MutableNode], nodeB.asInstanceOf[MutableNode])
def remove(node: ImmutableNode) = network.remove(node.asInstanceOf[MutableNode])
def node(address: String) = network.node(address)
def nodes = network.nodes.asJava
def nodes(reference: ImmutableNode) = network.nodes(reference).asJava
def neighbors(node: ImmutableNode) = network.neighbors(node).asJava
def sendToAddress(source: ImmutableNode, target: String, name: String, data: AnyRef*) =
network.sendToAddress(source, target, name, data: _*)
def sendToNeighbors(source: ImmutableNode, name: String, data: AnyRef*) =
network.sendToNeighbors(source, name, data: _*)
def sendToReachable(source: ImmutableNode, name: String, data: AnyRef*) =
network.sendToReachable(source, name, data: _*)
def sendToVisible(source: ImmutableNode, name: String, data: AnyRef*) =
network.sendToVisible(source, name, data: _*)
def globalBuffer = network.globalBuffer
def globalBuffer_=(value: Double) = network.globalBuffer = value
def globalBufferSize = network.globalBufferSize
def globalBufferSize_=(value: Double) = network.globalBufferSize = value
def addConnector(connector: Connector) = network.addConnector(connector)
def removeConnector(connector: Connector) = network.removeConnector(connector)
def changeBuffer(delta: Double) = network.changeBuffer(delta)
}
}