From f74b7218b055b595eded74d8a7e4f3bcc1633778 Mon Sep 17 00:00:00 2001 From: minjaesong Date: Sat, 1 Mar 2025 23:03:56 +0900 Subject: [PATCH] token ring stuff wip --- .../gameactors/FixtureRingBusExerciser.kt | 166 ++++++++++++++++++ .../modulebasegame/gameactors/Electric.kt | 1 + .../gameworld/IngameNetPacket.kt | 20 ++- .../modulebasegame/gameworld/PacketRunner.kt | 13 ++ 4 files changed, 192 insertions(+), 8 deletions(-) create mode 100644 ModuleComputers/src/net/torvald/terrarum/modulecomputers/gameactors/FixtureRingBusExerciser.kt diff --git a/ModuleComputers/src/net/torvald/terrarum/modulecomputers/gameactors/FixtureRingBusExerciser.kt b/ModuleComputers/src/net/torvald/terrarum/modulecomputers/gameactors/FixtureRingBusExerciser.kt new file mode 100644 index 000000000..274739810 --- /dev/null +++ b/ModuleComputers/src/net/torvald/terrarum/modulecomputers/gameactors/FixtureRingBusExerciser.kt @@ -0,0 +1,166 @@ +package net.torvald.terrarum.modulecomputers.gameactors + +import com.badlogic.gdx.utils.Queue +import net.torvald.random.HQRNG +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.serialise.Common +import org.dyn4j.geometry.Vector2 + +/** + * Created by minjaesong on 2025-03-01. + */ +class FixtureRingBusExerciser : Electric { + + @Transient private val rng = HQRNG() + + private val mac = rng.nextInt() + + companion object { + const val SIGNAL_TOO_WEAK_THRESHOLD = 1.0 / 16.0 + } + + constructor() : super( + BlockBox(BlockBox.NO_COLLISION, 2, 2), + nameFun = { Lang["ITEM_JUKEBOX"] }, + mainUI = UIRingBusExerciser() + ) + + private fun setEmitterAndSink() { + clearStatus() + setWireEmitterAt(0, 0, "10base2") + setWireSinkAt(1, 0, "10base2") + } + + init { + setEmitterAndSink() + } + + override fun reload() { + super.reload() + setEmitterAndSink() + } + + private val msgQueue = Queue>() + + + 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 + + override fun updateSignal() { + // monitor the input port + val inn = getWireStateAt(1, 0, "10base2") + + // if a signal is there + if (inn.x >= SIGNAL_TOO_WEAK_THRESHOLD) { + val packetNumber = (inn.y + 0.5).toInt() + + if (packetNumber != 0) { // packet number must be non-zero + // if not in abort state, process the incoming packets + if (!statusAbort) { + // fetch packet from the world + try { + val packet = getPacketByNumber(packetNumber) + + if (msgQueue.notEmpty() || packet.shouldIintercept(mac)) { + val newPacket = doSomethingWithPacket(packet) ?: packetNumber + setWireEmissionAt(0, 0, Vector2(1.0, newPacket.toDouble())) + + // mark the old packet as to be destroyed + if (newPacket != packetNumber) { + packet.discardPacket() + } + } + else { + setWireEmissionAt(0, 0, Vector2(1.0, packetNumber.toDouble())) + } + } + // packet lost due to poor savegame migration or something: send out ABORT signal + catch (e: NullPointerException) { + val abortPacket = IngameNetPacket.makeAbort(mac) + emitNewPacket(abortPacket) + statusAbort = true + } + } + // else, still watch for the new valid token + else { + // fetch packet from the world + try { + val packet = getPacketByNumber(packetNumber) + + // not an Active Monitor + if (activeMonitorStatus < 2) { + if (packet.getFrameType() == "token") { + // unlock myself and pass the token + statusAbort = false + setWireEmissionAt(0, 0, Vector2(1.0, packetNumber.toDouble())) + } + else if (packet.getFrameType() == "abort") { + // unlock myself (just in case) and pass the token + statusAbort = true + setWireEmissionAt(0, 0, Vector2(1.0, packetNumber.toDouble())) + } + else { + // discard anything that is not a token + setWireEmissionAt(0, 0, Vector2()) + } + } + // am Active Monitor + else { + if (packet.getFrameType() == "abort") { + // send out a new token + emitNewPacket(IngameNetPacket.makeToken(mac)) + statusAbort = false + } + else { + // discard anything that is not a token + setWireEmissionAt(0, 0, Vector2()) + } + } + } + // packet lost due to poor savegame migration or something: discard token + catch (e: NullPointerException) { + setWireEmissionAt(0, 0, Vector2()) + } + } + } + } + // if a signal is not there + else { + setWireEmissionAt(0, 0, Vector2()) + } + } + + protected fun doSomethingWithPacket(incomingPacket: IngameNetPacket): Int? { + return when (incomingPacket.getFrameType()) { + "token" -> doSomethingWithToken(incomingPacket) + "data" -> doSomethingWithData(incomingPacket) + "ack" -> doSomethingWithAck(incomingPacket) + "ballot" -> doSomethingWithBallot(incomingPacket) + "abort" -> 0 + else -> null /* returns the packet untouched */ + } + } + + private fun getPacketByNumber(number: Int) = (INGAME.world.extraFields["tokenring"] as PacketRunner)[number] + + private fun emitNewPacket(packet: IngameNetPacket): Int { + return (INGAME.world.extraFields["tokenring"] as PacketRunner).addPacket(packet).also { + setWireEmissionAt(0, 0, Vector2(1.0, it.toDouble())) + } + } + + protected fun doSomethingWithToken(incomingPacket: IngameNetPacket): 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) + } +} \ No newline at end of file diff --git a/src/net/torvald/terrarum/modulebasegame/gameactors/Electric.kt b/src/net/torvald/terrarum/modulebasegame/gameactors/Electric.kt index e9ee4e772..8d737461e 100644 --- a/src/net/torvald/terrarum/modulebasegame/gameactors/Electric.kt +++ b/src/net/torvald/terrarum/modulebasegame/gameactors/Electric.kt @@ -98,6 +98,7 @@ open class Electric : FixtureBase { /** Triggered when 'digital_bit' is held low. This function WILL NOT be triggered simultaneously with the falling edge. Level detection only considers the real component (labeled as 'x') of the vector */ //open fun onSignalLow(readFrom: BlockBoxIndex) {} + // called right after `onRisingEdge` and `onFallingEdge` in the `updateImpl` open fun updateSignal() {} fun getWireStateAt(offsetX: Int, offsetY: Int, sinkType: WireEmissionType): Vector2 { diff --git a/src/net/torvald/terrarum/modulebasegame/gameworld/IngameNetPacket.kt b/src/net/torvald/terrarum/modulebasegame/gameworld/IngameNetPacket.kt index 870e3b066..f06cfbdc4 100644 --- a/src/net/torvald/terrarum/modulebasegame/gameworld/IngameNetPacket.kt +++ b/src/net/torvald/terrarum/modulebasegame/gameworld/IngameNetPacket.kt @@ -11,7 +11,7 @@ import java.util.zip.CRC32 * ## The Header * * - (Byte1) Frame Type - * - 00 : invalid + * - 00 : invalid (mark the packet as "to be destroyed" by game) * - FF : token (an "empty" packet for a Token Ring) * - AA : data * - EE : abort @@ -107,6 +107,10 @@ data class IngameNetPacket(val byteArray: ByteArray) { return byteArray.toBigInt32(6) } + fun discardPacket() { + byteArray[0] = 0 + } + companion object { private fun ByteArray.makeHeader(frameType: Int, mac: Int): ByteArray { this[0] = frameType.toByte() @@ -114,26 +118,26 @@ data class IngameNetPacket(val byteArray: ByteArray) { return this } - fun makeToken(mac: Int) = ByteArray(5).makeHeader(0xff, mac) + fun makeToken(mac: Int) = IngameNetPacket(ByteArray(5).makeHeader(0xff, mac)) - fun makeAbort(mac: Int) = ByteArray(5).makeHeader(0xee, mac) + fun makeAbort(mac: Int) = IngameNetPacket(ByteArray(5).makeHeader(0xee, mac)) - fun makeBallot(mac: Int) = ByteArray(9).makeHeader(0x99, mac) + fun makeBallot(mac: Int) = IngameNetPacket(ByteArray(9).makeHeader(0x99, mac)) - fun makeData(sender: Int, recipient: Int, data: ByteArray) = ByteArray(18 + data.size).also { + fun makeData(sender: Int, recipient: Int, data: ByteArray) = IngameNetPacket(ByteArray(18 + data.size).also { it.makeHeader(0xaa, sender) it.writeBigInt32(recipient, 6) it.writeBigInt32(data.size, 10) System.arraycopy(data, 0, it, 14, data.size) val crc = CRC32().also { it.update(data) }.value.toInt() it.writeBigInt32(crc, 14 + data.size) - } + }) - fun makeAck(sender: Int, recipient: Int, status: Int = 0) = ByteArray(12).also { + fun makeAck(sender: Int, recipient: Int, status: Int = 0) = IngameNetPacket(ByteArray(12).also { it.makeHeader(0x55, sender) it.writeBigInt32(recipient, 6) it.writeBigInt16(status, 10) - } + }) } } \ No newline at end of file diff --git a/src/net/torvald/terrarum/modulebasegame/gameworld/PacketRunner.kt b/src/net/torvald/terrarum/modulebasegame/gameworld/PacketRunner.kt index 2ce583e4f..aa52954a3 100644 --- a/src/net/torvald/terrarum/modulebasegame/gameworld/PacketRunner.kt +++ b/src/net/torvald/terrarum/modulebasegame/gameworld/PacketRunner.kt @@ -1,5 +1,6 @@ package net.torvald.terrarum.modulebasegame.gameworld +import net.torvald.random.HQRNG import net.torvald.terrarum.gameworld.TerrarumSavegameExtrafieldSerialisable import java.util.TreeMap @@ -10,6 +11,8 @@ import java.util.TreeMap */ class PacketRunner : TerrarumSavegameExtrafieldSerialisable { + @Transient private val rng = HQRNG() + private val ledger = TreeMap() operator fun set(id: Int, packet: IngameNetPacket) { @@ -18,5 +21,15 @@ class PacketRunner : TerrarumSavegameExtrafieldSerialisable { operator fun get(id: Int) = ledger[id]!! + fun addPacket(packet: IngameNetPacket): Int { + var i = rng.nextInt() + while (ledger.containsKey(i)) { + i = rng.nextInt() + } + + ledger[i] = packet + + return i + } }