some cleanup of non-updating node block initialization; renamed router to switch; improved networking, packets now have a ttl, meaning they can only be bounced by switches up to 5 times, avoids stack overflows. they also store the origins address now, so packets will always give the real sender's address now, not the address of the last switch they passed through. switches also have an internal queue of size 20 now and will only forward one packet per 5 ticks, so a) latency is now a thing, b) packet drops are also a thing.

This commit is contained in:
Florian Nücke 2014-03-05 03:20:09 +01:00
parent 5f7360ff82
commit 6c1e15e1bd
14 changed files with 97 additions and 61 deletions

View File

@ -63,7 +63,6 @@ class Proxy {
TickRegistry.registerTickHandler(SimpleComponentTickHandler.Instance, Side.SERVER)
GameRegistry.registerPlayerTracker(Keyboard)
NetworkRegistry.instance.registerConnectionHandler(ConnectionHandler)
MinecraftForge.EVENT_BUS.register(Network)
MinecraftForge.EVENT_BUS.register(WirelessNetwork)
}
}

View File

@ -5,7 +5,7 @@ import li.cil.oc.server.TickHandler
import li.cil.oc.{api, common}
import net.minecraft.entity.player.EntityPlayer
class Cable extends Environment with Analyzable with PassiveNode {
class Cable extends Environment with Analyzable {
val node = api.Network.newNode(this, Visibility.None).create()
def onAnalyze(player: EntityPlayer, side: Int, hitX: Float, hitY: Float, hitZ: Float) = null

View File

@ -6,7 +6,7 @@ import li.cil.oc.{Settings, api}
import net.minecraftforge.common.ForgeDirection
import scala.collection.convert.WrapAsScala._
class Capacitor extends Environment with PassiveNode {
class Capacitor extends Environment {
// Start with maximum theoretical capacity, gets reduced after validation.
// This is done so that we don't lose energy while loading.
val node = api.Network.newNode(this, Visibility.Network).

View File

@ -8,7 +8,7 @@ import li.cil.oc.{api, Settings}
import net.minecraft.entity.player.EntityPlayer
import net.minecraft.item.ItemStack
class DiskDrive extends Environment with ComponentInventory with Rotatable with Analyzable with PassiveNode {
class DiskDrive extends Environment with ComponentInventory with Rotatable with Analyzable {
val node = api.Network.newNode(this, Visibility.None).create()
// ----------------------------------------------------------------------- //

View File

@ -1,15 +1,20 @@
package li.cil.oc.common.tileentity
import cpw.mods.fml.relauncher.{Side, SideOnly}
import li.cil.oc.api.network.{Node, Message, Visibility, SidedEnvironment}
import li.cil.oc.api.network._
import li.cil.oc.server.component.NetworkCard
import li.cil.oc.util.ExtendedNBT._
import li.cil.oc.{api, Settings}
import net.minecraft.nbt.NBTTagCompound
import net.minecraftforge.common.ForgeDirection
import scala.collection.mutable
import net.minecraft.entity.player.EntityPlayer
trait Hub extends Environment with SidedEnvironment {
trait Hub extends Environment with SidedEnvironment with Analyzable {
protected val plugs = ForgeDirection.VALID_DIRECTIONS.map(side => new Plug(side))
protected val queue = mutable.Queue.empty[(ForgeDirection, NetworkCard.Packet)]
// ----------------------------------------------------------------------- //
override def node = null
@ -21,11 +26,30 @@ trait Hub extends Environment with SidedEnvironment {
// ----------------------------------------------------------------------- //
override def onAnalyze(player: EntityPlayer, side: Int, hitX: Float, hitY: Float, hitZ: Float): Array[Node] = null
// ----------------------------------------------------------------------- //
override def updateEntity() {
super.updateEntity()
if (world.getWorldTime % 5 == 0 && queue.nonEmpty) {
val (sourceSide, packet) = queue.dequeue()
for (side <- ForgeDirection.VALID_DIRECTIONS if side != sourceSide) {
sidedNode(side).sendToReachable("network.message", packet)
}
}
}
override def readFromNBT(nbt: NBTTagCompound) {
super.readFromNBT(nbt)
nbt.getTagList(Settings.namespace + "plugs").iterator[NBTTagCompound].zip(plugs).foreach {
case (plugNbt, plug) => plug.node.load(plugNbt)
}
nbt.getTagList(Settings.namespace + "queue").foreach[NBTTagCompound](tag => {
val side = ForgeDirection.getOrientation(tag.getInteger("side"))
val packet = NetworkCard.loadPacket(tag)
queue += side -> packet
})
}
override def writeToNBT(nbt: NBTTagCompound) {
@ -37,6 +61,13 @@ trait Hub extends Environment with SidedEnvironment {
plug.node.save(plugNbt)
plugNbt
}))
nbt.setNewTagList(Settings.namespace + "queue", queue.map {
case (sourceSide, packet) =>
val tag = new NBTTagCompound()
tag.setInteger("side", sourceSide.ordinal())
packet.save(tag)
tag
})
}
}
@ -65,8 +96,9 @@ trait Hub extends Environment with SidedEnvironment {
protected def onPlugDisconnect(plug: Plug, node: Node) {}
protected def onPlugMessage(plug: Plug, message: Message) {
if (message.name == "network.message") {
plug.plugsInOtherNetworks.foreach(_.node.sendToReachable(message.name, message.data: _*))
if (message.name == "network.message") message.data match {
case Array(packet: NetworkCard.Packet) if packet.ttl > 0 && queue.size < 20 => queue += plug.side -> packet.hop()
case _ =>
}
}

View File

@ -9,7 +9,7 @@ import net.minecraft.entity.player.EntityPlayer
import net.minecraft.nbt.NBTTagCompound
import net.minecraftforge.common.ForgeDirection
class Keyboard(isRemote: Boolean) extends Environment with SidedEnvironment with Analyzable with Rotatable with PassiveNode {
class Keyboard(isRemote: Boolean) extends Environment with SidedEnvironment with Analyzable with Rotatable {
def this() = this(false)
val keyboard = if (isRemote) null

View File

@ -1,5 +0,0 @@
package li.cil.oc.common.tileentity
// Marker for tile entities that don't tick and therefore have to be added to
// the component network manually (see Network singleton).
trait PassiveNode

View File

@ -4,7 +4,7 @@ import cpw.mods.fml.common.Optional
import cpw.mods.fml.common.Optional.Method
import cpw.mods.fml.relauncher.{Side, SideOnly}
import li.cil.oc.api.Network
import li.cil.oc.api.network.{Connector, Visibility, Node, Analyzable}
import li.cil.oc.api.network.{Connector, Visibility, Node}
import li.cil.oc.common
import li.cil.oc.server.{PacketSender => ServerPacketSender, driver, component}
import li.cil.oc.util.ExtendedNBT._
@ -18,7 +18,7 @@ import stargatetech2.api.bus.IBusDevice
// See AbstractBusAware as to why we have to define the IBusDevice here.
@Optional.Interface(iface = "stargatetech2.api.bus.IBusDevice", modid = "StargateTech2")
class Rack extends Hub with PowerBalancer with Inventory with Rotatable with BundledRedstoneAware with AbstractBusAware with IBusDevice with Analyzable {
class Rack extends Hub with PowerBalancer with Inventory with Rotatable with BundledRedstoneAware with AbstractBusAware with IBusDevice {
val servers = Array.fill(getSizeInventory)(None: Option[component.Server])
val sides = Seq(ForgeDirection.UP, ForgeDirection.EAST, ForgeDirection.WEST, ForgeDirection.DOWN).

View File

@ -3,19 +3,10 @@ package li.cil.oc.common.tileentity
import cpw.mods.fml.common.{Loader, Optional}
import dan200.computer.api.{ILuaContext, IComputerAccess, IPeripheral}
import li.cil.oc.api.network.Message
import li.cil.oc.server.TickHandler
import scala.collection.mutable
@Optional.Interface(iface = "dan200.computer.api.IPeripheral", modid = "ComputerCraft")
class Router extends Hub with IPeripheral with PassiveNode {
override def canUpdate = false
override def validate() {
super.validate()
TickHandler.schedule(this)
}
class Router extends Hub with IPeripheral {
// ----------------------------------------------------------------------- //
// Peripheral

View File

@ -4,9 +4,10 @@ import li.cil.oc.Settings
import li.cil.oc.api.Network
import li.cil.oc.api.network._
import li.cil.oc.util.ExtendedNBT._
import net.minecraft.nbt.{NBTTagInt, NBTTagCompound}
import net.minecraft.nbt._
import scala.collection.convert.WrapAsScala._
import scala.collection.mutable
import scala.Some
class NetworkCard extends ManagedComponent {
val node = Network.newNode(this, Visibility.Network).
@ -50,7 +51,7 @@ class NetworkCard extends ManagedComponent {
val address = args.checkString(0)
val port = checkPort(args.checkInteger(1))
checkPacketSize(args.drop(2))
node.sendToAddress(address, "network.message", Seq(Int.box(port)) ++ args.drop(2): _*)
node.sendToReachable("network.message", new NetworkCard.Packet(node.address, Some(address), port, args.drop(2)))
result(true)
}
@ -58,7 +59,7 @@ class NetworkCard extends ManagedComponent {
def broadcast(context: Context, args: Arguments): Array[AnyRef] = {
val port = checkPort(args.checkInteger(0))
checkPacketSize(args.drop(1))
node.sendToReachable("network.message", Seq(Int.box(port)) ++ args.drop(1): _*)
node.sendToReachable("network.message", new NetworkCard.Packet(node.address, None, port, args.drop(1)))
result(true)
}
@ -79,8 +80,8 @@ class NetworkCard extends ManagedComponent {
if ((message.name == "computer.stopped" || message.name == "computer.started") && node.isNeighborOf(message.source))
openPorts.clear()
if (message.name == "network.message") message.data match {
case Array(port: Integer, args@_*) if openPorts.contains(port) =>
node.sendToReachable("computer.signal", Seq("modem_message", message.source.address, Int.box(port), Int.box(0)) ++ args: _*)
case Array(packet: NetworkCard.Packet) if packet.source != node.address && packet.dest.forall(_ == node.address) && openPorts.contains(packet.port) =>
node.sendToReachable("computer.signal", Seq("modem_message", packet.source, Int.box(packet.port), Int.box(0)) ++ packet.data: _*)
case _ =>
}
}
@ -121,3 +122,47 @@ class NetworkCard extends ManagedComponent {
}
}
}
object NetworkCard {
class Packet(val source: String, val dest: Option[String], val port: Int, val data: Iterable[AnyRef], val ttl: Int = 5) {
def hop() = new Packet(source, dest, port, data, ttl - 1)
def save(nbt: NBTTagCompound) {
nbt.setString("source", source)
nbt.setBoolean("broadcast", dest.isEmpty)
dest.foreach(nbt.setString("dest", _))
nbt.setInteger("port", port)
nbt.setInteger("ttl", ttl)
val dataArray = data.toArray
nbt.setInteger("dataLength", dataArray.length)
for (i <- 0 until dataArray.length) dataArray(i) match {
case null | Unit | None =>
case value: java.lang.Boolean => nbt.setBoolean("data" + i, value)
case value: java.lang.Double => nbt.setDouble("data" + i, value)
case value: java.lang.String => nbt.setString("data" + i, value)
case value: Array[Byte] => nbt.setByteArray("data" + i, value)
}
}
}
def loadPacket(nbt: NBTTagCompound) = {
val source = nbt.getString("source")
val dest =
if (nbt.getBoolean("broadcast")) None
else Option(nbt.getString("dest"))
val port = nbt.getInteger("port")
val ttl = nbt.getInteger("ttl")
val data = for (i <- 0 until nbt.getInteger("dataLength")) yield {
if (nbt.hasKey("data" + i)) {
nbt.getTag("data" + i) match {
case boolean: NBTTagByte => Boolean.box(boolean.data == 1)
case double: NBTTagDouble => Double.box(double.data)
case string: NBTTagString => string.data: AnyRef
case array: NBTTagByteArray => array.byteArray
}
}
else null
}
new Packet(source, dest, port, data, ttl)
}
}

View File

@ -42,7 +42,7 @@ class Machine(val owner: Owner, constructor: Constructor[_ <: Architecture]) ext
private val _users = mutable.Set.empty[String]
private val signals = new mutable.Queue[Machine.Signal]
private val signals = mutable.Queue.empty[Machine.Signal]
private val callCounts = mutable.Map.empty[String, mutable.Map[String, Int]]

View File

@ -8,15 +8,11 @@ import li.cil.oc.api.network.{Node => ImmutableNode, SidedEnvironment, Environme
import li.cil.oc.common.block.Cable
import li.cil.oc.common.multipart.CablePart
import li.cil.oc.common.tileentity
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
@ -362,28 +358,6 @@ private class Network private(private val data: mutable.Map[String, Network.Vert
}
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.isInvalid && !tileEntity.getWorldObj.isRemote) {
for (side <- ForgeDirection.VALID_DIRECTIONS) {

View File

@ -18,7 +18,7 @@ oc:tile.PowerDistributor.name=Stromverteiler
oc:tile.Redstone.name=Redstone-I/O
oc:tile.Robot.name=Roboter
oc:tile.RobotAfterimage.name=Roboter
oc:tile.Router.name=Router
oc:tile.Router.name=Switch
oc:tile.Screen0.name=Bildschirm (Stufe 1)
oc:tile.Screen1.name=Bildschirm (Stufe 2)
oc:tile.Screen2.name=Bildschirm (Stufe 3)
@ -158,7 +158,7 @@ oc:tooltip.Robot=Im Gegensatz zu normalen Computern können sich Roboter in der
# The underscore makes sure this isn't hidden with the rest of the tooltip.
oc:tooltip.Robot_Level=§fStufe§7: §a%s§7.
oc:tooltip.Robot_StoredEnergy=§fGespeicherte Energie§7: §a%s§7.
oc:tooltip.Router=Erlaubt es, mehrere Netzwerke miteinander zu verbinden. Leitet ausschließlich Netzwerknachrichten weiter, Komponenten "hinter" dem Router sind nicht sichtbar. Nützlich, um Netzwerke zu trennen, jedoch nach wie vor Kommunikation zwischen den Netzwerken zu erlauben, z.b. mittels Netzwerkkarten.
oc:tooltip.Router=Erlaubt es, mehrere Netzwerke miteinander zu verbinden. Leitet ausschließlich Netzwerknachrichten weiter, Komponenten "hinter" dem Switch sind nicht sichtbar. Nützlich, um Netzwerke zu trennen, jedoch nach wie vor Kommunikation zwischen den Netzwerken zu erlauben, z.b. mittels Netzwerkkarten.
oc:tooltip.Screen=Zeigt Text an, gesteuert von Grafikkarten in Computern.[nl] Höchstauflösung: §f%sx%s§7.[nl] Maximale Farbtiefe: §f%s§7.
oc:tooltip.Server=Ein Server kann wie ein gewöhnliches Computergehäuse mit Komponenten verbessert werden. Um den Server zu starten, muss er in einem Servergehäuse installiert werden.[nl] Anzahl unterstützter Fernbedienungen: §f%s§7.
oc:tooltip.Server.Components=Installierte Komponenten:

View File

@ -18,7 +18,7 @@ oc:tile.PowerDistributor.name=Power Distributor
oc:tile.Redstone.name=Redstone I/O
oc:tile.Robot.name=Robot
oc:tile.RobotAfterimage.name=Robot
oc:tile.Router.name=Router
oc:tile.Router.name=Switch
oc:tile.Screen0.name=Screen (Tier 1)
oc:tile.Screen1.name=Screen (Tier 2)
oc:tile.Screen2.name=Screen (Tier 3)