token ring stuff and renaming things to avoid confusion

This commit is contained in:
minjaesong
2025-03-02 15:04:53 +09:00
parent f74b7218b0
commit ef4a5d6eb9
3 changed files with 151 additions and 63 deletions

View File

@@ -6,10 +6,11 @@ import net.torvald.terrarum.INGAME
import net.torvald.terrarum.langpack.Lang import net.torvald.terrarum.langpack.Lang
import net.torvald.terrarum.modulebasegame.gameactors.BlockBox import net.torvald.terrarum.modulebasegame.gameactors.BlockBox
import net.torvald.terrarum.modulebasegame.gameactors.Electric import net.torvald.terrarum.modulebasegame.gameactors.Electric
import net.torvald.terrarum.modulebasegame.gameworld.IngameNetPacket import net.torvald.terrarum.modulebasegame.gameworld.NetFrame
import net.torvald.terrarum.modulebasegame.gameworld.PacketRunner import net.torvald.terrarum.modulebasegame.gameworld.NetRunner
import net.torvald.terrarum.serialise.Common import net.torvald.terrarum.serialise.Common
import org.dyn4j.geometry.Vector2 import org.dyn4j.geometry.Vector2
import kotlin.math.sign
/** /**
* Created by minjaesong on 2025-03-01. * Created by minjaesong on 2025-03-01.
@@ -46,9 +47,9 @@ class FixtureRingBusExerciser : Electric {
} }
private val msgQueue = Queue<Pair<Int, String>>() private val msgQueue = Queue<Pair<Int, String>>()
private val msgLog = Queue<Pair<Int, String>>()
private var statusAbort = false // will "eat away" any receiving frames unless the frame is a ballot frame
private var statusAbort = false // will "eat away" any receiving packets unless the packet is a ballot packet
private var activeMonitorStatus = 0 // 0: unknown, 1: known and not me, 2: known and it's me private var activeMonitorStatus = 0 // 0: unknown, 1: known and not me, 2: known and it's me
override fun updateSignal() { override fun updateSignal() {
@@ -57,77 +58,81 @@ class FixtureRingBusExerciser : Electric {
// if a signal is there // if a signal is there
if (inn.x >= SIGNAL_TOO_WEAK_THRESHOLD) { 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 (frameNumber != 0) { // frame number must be non-zero
// if not in abort state, process the incoming packets // if not in abort state, process the incoming frames
if (!statusAbort) { if (!statusAbort) {
// fetch packet from the world // fetch frame from the world
try { try {
val packet = getPacketByNumber(packetNumber) val frame = getFrameByNumber(frameNumber)
if (msgQueue.notEmpty() || packet.shouldIintercept(mac)) { if (msgQueue.notEmpty() || frame.shouldIintercept(mac)) {
val newPacket = doSomethingWithPacket(packet) ?: packetNumber // do something with the received frame
setWireEmissionAt(0, 0, Vector2(1.0, newPacket.toDouble())) val newFrame = doSomethingWithFrame(frame) ?: frameNumber
setWireEmissionAt(0, 0, Vector2(1.0, newFrame.toDouble()))
// mark the old packet as to be destroyed // if the "do something" processs returns a new frame, mark the old frame as to be destroyed
if (newPacket != packetNumber) { if (newFrame != frameNumber) {
packet.discardPacket() frame.discardFrame()
} }
} }
else { 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) { catch (e: NullPointerException) {
val abortPacket = IngameNetPacket.makeAbort(mac) val abortFrame = NetFrame.makeAbort(mac)
emitNewPacket(abortPacket) emitNewFrame(abortFrame)
statusAbort = true statusAbort = true
} }
} }
// else, still watch for the new valid token // else, still watch for the new valid token
else { else {
// fetch packet from the world // fetch frame from the world
try { try {
val packet = getPacketByNumber(packetNumber) val frame = getFrameByNumber(frameNumber)
// not an Active Monitor // not an Active Monitor
if (activeMonitorStatus < 2) { if (activeMonitorStatus < 2) {
if (packet.getFrameType() == "token") { if (frame.getFrameType() == "token") {
// unlock myself and pass the token // unlock myself and pass the token
statusAbort = false statusAbort = false
setWireEmissionAt(0, 0, Vector2(1.0, packetNumber.toDouble())) setWireEmissionAt(0, 0, Vector2(1.0, frameNumber.toDouble()))
} }
else if (packet.getFrameType() == "abort") { else if (frame.getFrameType() == "abort") {
// unlock myself (just in case) and pass the token // lock myself (just in case) and pass the token
statusAbort = true statusAbort = true
setWireEmissionAt(0, 0, Vector2(1.0, packetNumber.toDouble())) setWireEmissionAt(0, 0, Vector2(1.0, frameNumber.toDouble()))
} }
else { else {
// discard anything that is not a token // discard anything that is not a token or yet another abort
setWireEmissionAt(0, 0, Vector2()) setWireEmissionAt(0, 0, Vector2())
} }
} }
// am Active Monitor // am Active Monitor
else { else {
if (packet.getFrameType() == "abort") { if (frame.getFrameType() == "abort") {
// send out a new token // send out a new token
emitNewPacket(IngameNetPacket.makeToken(mac)) emitNewFrame(NetFrame.makeToken(mac))
statusAbort = false statusAbort = false
} }
else { else {
// discard anything that is not a token // discard anything that is not an abort
setWireEmissionAt(0, 0, Vector2()) 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) { catch (e: NullPointerException) {
setWireEmissionAt(0, 0, Vector2()) setWireEmissionAt(0, 0, Vector2())
} }
} }
} }
else {
setWireEmissionAt(0, 0, Vector2())
}
} }
// if a signal is not there // if a signal is not there
else { else {
@@ -135,32 +140,104 @@ class FixtureRingBusExerciser : Electric {
} }
} }
protected fun doSomethingWithPacket(incomingPacket: IngameNetPacket): Int? { protected fun doSomethingWithFrame(incomingFrame: NetFrame): Int? {
return when (incomingPacket.getFrameType()) { return when (incomingFrame.getFrameType()) {
"token" -> doSomethingWithToken(incomingPacket) "token" -> doSomethingWithToken(incomingFrame)
"data" -> doSomethingWithData(incomingPacket) "data" -> doSomethingWithData(incomingFrame)
"ack" -> doSomethingWithAck(incomingPacket) "ack" -> doSomethingWithAck(incomingFrame)
"ballot" -> doSomethingWithBallot(incomingPacket) "ballot" -> doSomethingWithBallot(incomingFrame)
"abort" -> 0 "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 { private fun emitNewFrame(frame: NetFrame): Int {
return (INGAME.world.extraFields["tokenring"] as PacketRunner).addPacket(packet).also { return (INGAME.world.extraFields["tokenring"] as NetRunner).addFrame(frame).also {
setWireEmissionAt(0, 0, Vector2(1.0, it.toDouble())) 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 if (msgQueue.isEmpty) return null
val (recipient, msgStr) = msgQueue.removeFirst() val (recipient, msgStr) = msgQueue.removeFirst()
val msgByte = msgStr.toByteArray(Common.CHARSET) val msgByte = msgStr.toByteArray(Common.CHARSET)
val newPacket = IngameNetPacket.makeData(mac, recipient, msgByte) val newFrame = NetFrame.makeData(mac, recipient, msgByte)
return emitNewPacket(newPacket) 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
} }
} }

View File

@@ -4,18 +4,20 @@ import net.torvald.terrarum.serialise.*
import java.util.zip.CRC32 import java.util.zip.CRC32
/** /**
* # Packet data structure * This class manages Layer 2 of the OSI Model, the Frame.
*
* # Frame data structure
* *
* Endianness: big * Endianness: big
* *
* ## The Header * ## The Header
* *
* - (Byte1) Frame Type * - (Byte1) Frame Type
* - 00 : invalid (mark the packet as "to be destroyed" by game) * - 00 : invalid (mark the frame as "to be destroyed" by game)
* - FF : token (an "empty" packet for a Token Ring) * - FF : token (an "empty" frame for a Token Ring)
* - AA : data * - AA : data
* - EE : abort * - 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 * - (Byte1) Frame number. Always 0 for a Token Ring
* - (Byte4) Sender MAC address * - (Byte4) Sender MAC address
* *
@@ -30,8 +32,8 @@ import java.util.zip.CRC32
* ### Ballot * ### Ballot
* *
* - (Byte4) Currently elected Monitor candidate. A NIC examines this number, and if its MAC is larger than * - (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 * this value, the NIC writes its own MAC to this area and passes the frame to the next NIC; otherwise it
* just passes the packet as-is * just passes the frame as-is
* *
* ### Data * ### Data
* *
@@ -47,7 +49,7 @@ import java.util.zip.CRC32
* *
* Created by minjaesong on 2025-02-27. * Created by minjaesong on 2025-02-27.
*/ */
data class IngameNetPacket(val byteArray: ByteArray) { data class NetFrame(val byteArray: ByteArray) {
fun getFrameType(): String { fun getFrameType(): String {
return when (byteArray.first().toUint()) { 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 checkIsAbort() { if (getFrameType() != "abort") throw Error() }
private fun checkIsBallot() { if (getFrameType() != "ballot") 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 { fun getBallot(): Int {
checkIsBallot() checkIsBallot()
return byteArray.toBigInt32(6) return byteArray.toBigInt32(6)
@@ -79,7 +85,7 @@ data class IngameNetPacket(val byteArray: ByteArray) {
} }
fun shouldIintercept(mac: Int) = when (getFrameType()) { fun shouldIintercept(mac: Int) = when (getFrameType()) {
"ballot" -> (getBallot() < mac) "ballot" -> true//(getBallot() < mac)
"data", "ack" -> (getDataRecipient() == mac) "data", "ack" -> (getDataRecipient() == mac)
else -> false else -> false
} }
@@ -107,7 +113,7 @@ data class IngameNetPacket(val byteArray: ByteArray) {
return byteArray.toBigInt32(6) return byteArray.toBigInt32(6)
} }
fun discardPacket() { fun discardFrame() {
byteArray[0] = 0 byteArray[0] = 0
} }
@@ -118,13 +124,18 @@ data class IngameNetPacket(val byteArray: ByteArray) {
return this 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.makeHeader(0xaa, sender)
it.writeBigInt32(recipient, 6) it.writeBigInt32(recipient, 6)
it.writeBigInt32(data.size, 10) it.writeBigInt32(data.size, 10)
@@ -133,7 +144,7 @@ data class IngameNetPacket(val byteArray: ByteArray) {
it.writeBigInt32(crc, 14 + data.size) 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.makeHeader(0x55, sender)
it.writeBigInt32(recipient, 6) it.writeBigInt32(recipient, 6)
it.writeBigInt16(status, 10) it.writeBigInt16(status, 10)

View File

@@ -9,25 +9,25 @@ import java.util.TreeMap
* *
* Created by minjaesong on 2025-02-27. * Created by minjaesong on 2025-02-27.
*/ */
class PacketRunner : TerrarumSavegameExtrafieldSerialisable { class NetRunner : TerrarumSavegameExtrafieldSerialisable {
@Transient private val rng = HQRNG() @Transient private val rng = HQRNG()
private val ledger = TreeMap<Int, IngameNetPacket>() private val ledger = TreeMap<Int, NetFrame>()
operator fun set(id: Int, packet: IngameNetPacket) { operator fun set(id: Int, packet: NetFrame) {
ledger[id] = packet ledger[id] = packet
} }
operator fun get(id: Int) = ledger[id]!! operator fun get(id: Int) = ledger[id]!!
fun addPacket(packet: IngameNetPacket): Int { fun addFrame(frame: NetFrame): Int {
var i = rng.nextInt() var i = rng.nextInt()
while (ledger.containsKey(i)) { while (ledger.containsKey(i)) {
i = rng.nextInt() i = rng.nextInt()
} }
ledger[i] = packet ledger[i] = frame
return i return i
} }