diff --git a/ModuleComputers/src/net/torvald/terrarum/modulecomputers/gameactors/FixtureRingBusExerciser.kt b/ModuleComputers/src/net/torvald/terrarum/modulecomputers/gameactors/FixtureRingBusExerciser.kt index 274739810..a082dc46d 100644 --- a/ModuleComputers/src/net/torvald/terrarum/modulecomputers/gameactors/FixtureRingBusExerciser.kt +++ b/ModuleComputers/src/net/torvald/terrarum/modulecomputers/gameactors/FixtureRingBusExerciser.kt @@ -6,10 +6,11 @@ import net.torvald.terrarum.INGAME import net.torvald.terrarum.langpack.Lang import net.torvald.terrarum.modulebasegame.gameactors.BlockBox import net.torvald.terrarum.modulebasegame.gameactors.Electric -import net.torvald.terrarum.modulebasegame.gameworld.IngameNetPacket -import net.torvald.terrarum.modulebasegame.gameworld.PacketRunner +import net.torvald.terrarum.modulebasegame.gameworld.NetFrame +import net.torvald.terrarum.modulebasegame.gameworld.NetRunner import net.torvald.terrarum.serialise.Common import org.dyn4j.geometry.Vector2 +import kotlin.math.sign /** * Created by minjaesong on 2025-03-01. @@ -46,9 +47,9 @@ class FixtureRingBusExerciser : Electric { } private val msgQueue = Queue>() + private val msgLog = Queue>() - - private var statusAbort = false // will "eat away" any receiving packets unless the packet is a ballot packet + private var statusAbort = false // will "eat away" any receiving frames unless the frame is a ballot frame private var activeMonitorStatus = 0 // 0: unknown, 1: known and not me, 2: known and it's me override fun updateSignal() { @@ -57,77 +58,81 @@ class FixtureRingBusExerciser : Electric { // if a signal is there if (inn.x >= SIGNAL_TOO_WEAK_THRESHOLD) { - val packetNumber = (inn.y + 0.5).toInt() + val frameNumber = (inn.y + (0.5 * inn.y.sign)).toInt() - if (packetNumber != 0) { // packet number must be non-zero - // if not in abort state, process the incoming packets + if (frameNumber != 0) { // frame number must be non-zero + // if not in abort state, process the incoming frames if (!statusAbort) { - // fetch packet from the world + // fetch frame from the world try { - val packet = getPacketByNumber(packetNumber) + val frame = getFrameByNumber(frameNumber) - if (msgQueue.notEmpty() || packet.shouldIintercept(mac)) { - val newPacket = doSomethingWithPacket(packet) ?: packetNumber - setWireEmissionAt(0, 0, Vector2(1.0, newPacket.toDouble())) + if (msgQueue.notEmpty() || frame.shouldIintercept(mac)) { + // do something with the received frame + val newFrame = doSomethingWithFrame(frame) ?: frameNumber + setWireEmissionAt(0, 0, Vector2(1.0, newFrame.toDouble())) - // mark the old packet as to be destroyed - if (newPacket != packetNumber) { - packet.discardPacket() + // if the "do something" processs returns a new frame, mark the old frame as to be destroyed + if (newFrame != frameNumber) { + frame.discardFrame() } } else { - setWireEmissionAt(0, 0, Vector2(1.0, packetNumber.toDouble())) + setWireEmissionAt(0, 0, Vector2(1.0, frameNumber.toDouble())) } } - // packet lost due to poor savegame migration or something: send out ABORT signal + // frame lost due to poor savegame migration or something: send out ABORT signal catch (e: NullPointerException) { - val abortPacket = IngameNetPacket.makeAbort(mac) - emitNewPacket(abortPacket) + val abortFrame = NetFrame.makeAbort(mac) + emitNewFrame(abortFrame) statusAbort = true } } // else, still watch for the new valid token else { - // fetch packet from the world + // fetch frame from the world try { - val packet = getPacketByNumber(packetNumber) + val frame = getFrameByNumber(frameNumber) // not an Active Monitor if (activeMonitorStatus < 2) { - if (packet.getFrameType() == "token") { + if (frame.getFrameType() == "token") { // unlock myself and pass the token statusAbort = false - setWireEmissionAt(0, 0, Vector2(1.0, packetNumber.toDouble())) + setWireEmissionAt(0, 0, Vector2(1.0, frameNumber.toDouble())) } - else if (packet.getFrameType() == "abort") { - // unlock myself (just in case) and pass the token + else if (frame.getFrameType() == "abort") { + // lock myself (just in case) and pass the token statusAbort = true - setWireEmissionAt(0, 0, Vector2(1.0, packetNumber.toDouble())) + setWireEmissionAt(0, 0, Vector2(1.0, frameNumber.toDouble())) } else { - // discard anything that is not a token + // discard anything that is not a token or yet another abort setWireEmissionAt(0, 0, Vector2()) } } // am Active Monitor else { - if (packet.getFrameType() == "abort") { + if (frame.getFrameType() == "abort") { // send out a new token - emitNewPacket(IngameNetPacket.makeToken(mac)) + emitNewFrame(NetFrame.makeToken(mac)) statusAbort = false } else { - // discard anything that is not a token + // discard anything that is not an abort setWireEmissionAt(0, 0, Vector2()) } } } - // packet lost due to poor savegame migration or something: discard token + // frame lost due to poor savegame migration or something: discard token catch (e: NullPointerException) { setWireEmissionAt(0, 0, Vector2()) } } } + else { + setWireEmissionAt(0, 0, Vector2()) + } } // if a signal is not there else { @@ -135,32 +140,104 @@ class FixtureRingBusExerciser : Electric { } } - protected fun doSomethingWithPacket(incomingPacket: IngameNetPacket): Int? { - return when (incomingPacket.getFrameType()) { - "token" -> doSomethingWithToken(incomingPacket) - "data" -> doSomethingWithData(incomingPacket) - "ack" -> doSomethingWithAck(incomingPacket) - "ballot" -> doSomethingWithBallot(incomingPacket) + protected fun doSomethingWithFrame(incomingFrame: NetFrame): Int? { + return when (incomingFrame.getFrameType()) { + "token" -> doSomethingWithToken(incomingFrame) + "data" -> doSomethingWithData(incomingFrame) + "ack" -> doSomethingWithAck(incomingFrame) + "ballot" -> doSomethingWithBallot(incomingFrame) "abort" -> 0 - else -> null /* returns the packet untouched */ + else -> null /* returns the frame untouched */ } } - private fun getPacketByNumber(number: Int) = (INGAME.world.extraFields["tokenring"] as PacketRunner)[number] + private fun getFrameByNumber(number: Int) = (INGAME.world.extraFields["tokenring"] as NetRunner)[number] - private fun emitNewPacket(packet: IngameNetPacket): Int { - return (INGAME.world.extraFields["tokenring"] as PacketRunner).addPacket(packet).also { + private fun emitNewFrame(frame: NetFrame): Int { + return (INGAME.world.extraFields["tokenring"] as NetRunner).addFrame(frame).also { setWireEmissionAt(0, 0, Vector2(1.0, it.toDouble())) } } - protected fun doSomethingWithToken(incomingPacket: IngameNetPacket): Int? { + protected fun doSomethingWithToken(incomingFrame: NetFrame): Int? { if (msgQueue.isEmpty) return null val (recipient, msgStr) = msgQueue.removeFirst() val msgByte = msgStr.toByteArray(Common.CHARSET) - val newPacket = IngameNetPacket.makeData(mac, recipient, msgByte) - return emitNewPacket(newPacket) + val newFrame = NetFrame.makeData(mac, recipient, msgByte) + return emitNewFrame(newFrame) + } + + protected fun doSomethingWithData(incomingFrame: NetFrame): Int? { + val rec = incomingFrame.getDataRecipient() + // if the message is for me, put incoming message into queue, then send out ack + if (rec == mac) { + val str = incomingFrame.getDataContents()?.toString(Common.CHARSET) + msgLog.addLast(rec to (str ?: "(null)")) + + // make ack + val ack = NetFrame.makeAck(mac, incomingFrame.getSender()) + return emitNewFrame(ack) + } + else return null + } + + protected fun doSomethingWithAck(incomingFrame: NetFrame): Int? { + if (msgQueue.isEmpty) return null + + val topMsg = msgQueue.first() + + // if the ACK is sent to me... + if (incomingFrame.getDataRecipient() == mac && incomingFrame.getSender() == topMsg.first) { + + // ack or nak? + val successful = (incomingFrame.getAckStatus() == 0) + + // if successful, remove the message from the queue, then send out empty token + // if failed, keep the message, then send out empty token anyway + if (successful) { + msgQueue.removeFirst() + } + + // make an empty token + val token = NetFrame.makeToken(mac) + return emitNewFrame(token) + } + else return null + } + + protected fun doSomethingWithBallot(incomingFrame: NetFrame): Int? { + val ballotStatus = incomingFrame.getFrameNumber() + + // frame is in election phase + if (ballotStatus == 0) { + // if i'm also in the election phase, participate + if (activeMonitorStatus == 0) { + if (incomingFrame.getBallot() < mac) { + incomingFrame.setBallot(mac) + } + + // check if the election must be finished + if (incomingFrame.getSender() == mac && incomingFrame.getBallot() == mac) { + activeMonitorStatus = 2 + + // send out winner announcement + val win = NetFrame.makeWinnerAnnouncement(mac) + return emitNewFrame(win) + } + } + // if i'm in the winner announcement phase, kill the frame + else { + incomingFrame.discardFrame() + return 0 + } + } + // frame is in winner announcement phase + else if (ballotStatus == 1) { + activeMonitorStatus = 1 + } + + return null } } \ No newline at end of file diff --git a/src/net/torvald/terrarum/modulebasegame/gameworld/IngameNetPacket.kt b/src/net/torvald/terrarum/modulebasegame/gameworld/NetFrame.kt similarity index 77% rename from src/net/torvald/terrarum/modulebasegame/gameworld/IngameNetPacket.kt rename to src/net/torvald/terrarum/modulebasegame/gameworld/NetFrame.kt index f06cfbdc4..99ffec22f 100644 --- a/src/net/torvald/terrarum/modulebasegame/gameworld/IngameNetPacket.kt +++ b/src/net/torvald/terrarum/modulebasegame/gameworld/NetFrame.kt @@ -4,18 +4,20 @@ import net.torvald.terrarum.serialise.* import java.util.zip.CRC32 /** - * # Packet data structure + * This class manages Layer 2 of the OSI Model, the Frame. + * + * # Frame data structure * * Endianness: big * * ## The Header * * - (Byte1) Frame Type - * - 00 : invalid (mark the packet as "to be destroyed" by game) - * - FF : token (an "empty" packet for a Token Ring) + * - 00 : invalid (mark the frame as "to be destroyed" by game) + * - FF : token (an "empty" frame for a Token Ring) * - AA : data * - EE : abort - * - 99 : ballot (an initialiser packet for electing the Active Monitor for a Token Ring) + * - 99 : ballot (an initialiser frame for electing the Active Monitor for a Token Ring) * - (Byte1) Frame number. Always 0 for a Token Ring * - (Byte4) Sender MAC address * @@ -30,8 +32,8 @@ import java.util.zip.CRC32 * ### Ballot * * - (Byte4) Currently elected Monitor candidate. A NIC examines this number, and if its MAC is larger than - * this value, the NIC writes its own MAC to this area and passes the packet to the next NIC; otherwise it - * just passes the packet as-is + * this value, the NIC writes its own MAC to this area and passes the frame to the next NIC; otherwise it + * just passes the frame as-is * * ### Data * @@ -47,7 +49,7 @@ import java.util.zip.CRC32 * * Created by minjaesong on 2025-02-27. */ -data class IngameNetPacket(val byteArray: ByteArray) { +data class NetFrame(val byteArray: ByteArray) { fun getFrameType(): String { return when (byteArray.first().toUint()) { @@ -68,6 +70,10 @@ data class IngameNetPacket(val byteArray: ByteArray) { private fun checkIsAbort() { if (getFrameType() != "abort") throw Error() } private fun checkIsBallot() { if (getFrameType() != "ballot") throw Error() } + fun getSender(): Int = byteArray.toBigInt32(2) + + fun getFrameNumber(): Int = byteArray[1].toUint() + fun getBallot(): Int { checkIsBallot() return byteArray.toBigInt32(6) @@ -79,7 +85,7 @@ data class IngameNetPacket(val byteArray: ByteArray) { } fun shouldIintercept(mac: Int) = when (getFrameType()) { - "ballot" -> (getBallot() < mac) + "ballot" -> true//(getBallot() < mac) "data", "ack" -> (getDataRecipient() == mac) else -> false } @@ -107,7 +113,7 @@ data class IngameNetPacket(val byteArray: ByteArray) { return byteArray.toBigInt32(6) } - fun discardPacket() { + fun discardFrame() { byteArray[0] = 0 } @@ -118,13 +124,18 @@ data class IngameNetPacket(val byteArray: ByteArray) { return this } - fun makeToken(mac: Int) = IngameNetPacket(ByteArray(5).makeHeader(0xff, mac)) + fun makeToken(mac: Int) = NetFrame(ByteArray(5).makeHeader(0xff, mac)) - fun makeAbort(mac: Int) = IngameNetPacket(ByteArray(5).makeHeader(0xee, mac)) + fun makeAbort(mac: Int) = NetFrame(ByteArray(5).makeHeader(0xee, mac)) - fun makeBallot(mac: Int) = IngameNetPacket(ByteArray(9).makeHeader(0x99, mac)) + fun makeBallot(mac: Int) = NetFrame(ByteArray(9).makeHeader(0x99, mac)) - fun makeData(sender: Int, recipient: Int, data: ByteArray) = IngameNetPacket(ByteArray(18 + data.size).also { + fun makeWinnerAnnouncement(mac: Int) = NetFrame(ByteArray(9).makeHeader(0x99, mac).also { + it[1] = 1 + it.writeBigInt32(mac, 6) + }) + + fun makeData(sender: Int, recipient: Int, data: ByteArray) = NetFrame(ByteArray(18 + data.size).also { it.makeHeader(0xaa, sender) it.writeBigInt32(recipient, 6) it.writeBigInt32(data.size, 10) @@ -133,7 +144,7 @@ data class IngameNetPacket(val byteArray: ByteArray) { it.writeBigInt32(crc, 14 + data.size) }) - fun makeAck(sender: Int, recipient: Int, status: Int = 0) = IngameNetPacket(ByteArray(12).also { + fun makeAck(sender: Int, recipient: Int, status: Int = 0) = NetFrame(ByteArray(12).also { it.makeHeader(0x55, sender) it.writeBigInt32(recipient, 6) it.writeBigInt16(status, 10) diff --git a/src/net/torvald/terrarum/modulebasegame/gameworld/PacketRunner.kt b/src/net/torvald/terrarum/modulebasegame/gameworld/NetRunner.kt similarity index 70% rename from src/net/torvald/terrarum/modulebasegame/gameworld/PacketRunner.kt rename to src/net/torvald/terrarum/modulebasegame/gameworld/NetRunner.kt index aa52954a3..70c2a3598 100644 --- a/src/net/torvald/terrarum/modulebasegame/gameworld/PacketRunner.kt +++ b/src/net/torvald/terrarum/modulebasegame/gameworld/NetRunner.kt @@ -9,25 +9,25 @@ import java.util.TreeMap * * Created by minjaesong on 2025-02-27. */ -class PacketRunner : TerrarumSavegameExtrafieldSerialisable { +class NetRunner : TerrarumSavegameExtrafieldSerialisable { @Transient private val rng = HQRNG() - private val ledger = TreeMap() + private val ledger = TreeMap() - operator fun set(id: Int, packet: IngameNetPacket) { + operator fun set(id: Int, packet: NetFrame) { ledger[id] = packet } operator fun get(id: Int) = ledger[id]!! - fun addPacket(packet: IngameNetPacket): Int { + fun addFrame(frame: NetFrame): Int { var i = rng.nextInt() while (ledger.containsKey(i)) { i = rng.nextInt() } - ledger[i] = packet + ledger[i] = frame return i }