mirror of
https://github.com/curioustorvald/Terrarum.git
synced 2026-03-07 12:21:52 +09:00
token ring stuff and renaming things to avoid confusion
This commit is contained in:
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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)
|
||||||
@@ -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
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user