From 24a16426eddb214ab3b3cac5bd827c44929cbc33 Mon Sep 17 00:00:00 2001 From: minjaesong Date: Wed, 10 Jul 2019 20:49:20 +0900 Subject: [PATCH] fixed MDA scroll behaviour --- .../terrarum/gameactors/ai/AILuaAPI.kt | 10 +- .../computer/LoadTerrarumTermLib.kt | 76 ++++++ .../virtualcomputer/computer/LuaComputerVM.kt | 222 ++++++++++++++++-- .../virtualcomputer/computer/MDA.kt | 50 +++- .../virtualcomputer/peripheral/CommLine.kt | 37 +++ .../standalone/StandaloneApp.kt | 1 + 6 files changed, 362 insertions(+), 34 deletions(-) create mode 100644 src/net/torvald/terrarum/modulecomputers/virtualcomputer/computer/LoadTerrarumTermLib.kt create mode 100644 src/net/torvald/terrarum/modulecomputers/virtualcomputer/peripheral/CommLine.kt diff --git a/src/net/torvald/terrarum/gameactors/ai/AILuaAPI.kt b/src/net/torvald/terrarum/gameactors/ai/AILuaAPI.kt index 77070ac6f..0d174b648 100644 --- a/src/net/torvald/terrarum/gameactors/ai/AILuaAPI.kt +++ b/src/net/torvald/terrarum/gameactors/ai/AILuaAPI.kt @@ -1,5 +1,6 @@ package net.torvald.terrarum.gameactors.ai +import org.luaj.vm2.LuaTable import org.luaj.vm2.LuaValue /** @@ -385,6 +386,13 @@ import org.luaj.vm2.LuaValue fun Double.toLua() = LuaValue.valueOf(this) fun Int.toLua() = LuaValue.valueOf(this) fun String.toLua() = LuaValue.valueOf(this) +fun Boolean.toLua() = LuaValue.valueOf(this) fun Double?.toLua() = if (this == null) LuaValue.NIL else this.toLua() fun Int?.toLua() = if (this == null) LuaValue.NIL else this.toLua() -fun String?.toLua() = if (this == null) LuaValue.NIL else this.toLua() \ No newline at end of file +fun String?.toLua() = if (this == null) LuaValue.NIL else this.toLua() +fun Boolean?.toLua() = if (this == null) LuaValue.NIL else this.toLua() +fun luaTableOf(vararg luaValues: LuaValue): LuaTable { + val t = LuaTable.tableOf() + luaValues.forEachIndexed { index, luaValue -> t[index + 1] = luaValue } + return t +} \ No newline at end of file diff --git a/src/net/torvald/terrarum/modulecomputers/virtualcomputer/computer/LoadTerrarumTermLib.kt b/src/net/torvald/terrarum/modulecomputers/virtualcomputer/computer/LoadTerrarumTermLib.kt new file mode 100644 index 000000000..6eba95c67 --- /dev/null +++ b/src/net/torvald/terrarum/modulecomputers/virtualcomputer/computer/LoadTerrarumTermLib.kt @@ -0,0 +1,76 @@ +package net.torvald.terrarum.modulecomputers.virtualcomputer.computer + +import net.torvald.terrarum.gameactors.ai.luaTableOf +import net.torvald.terrarum.gameactors.ai.toLua +import org.luaj.vm2.Globals +import org.luaj.vm2.LuaError +import org.luaj.vm2.LuaValue + +/** + * Created by minjaesong on 2019-07-10. + */ +object LoadTerrarumTermLib { + + operator fun invoke(globals: Globals, terminal: MDA) { + globals.addZeroArgFun("term.getCursor") { + luaTableOf( + (terminal.cursor % terminal.width).toLua(), + (terminal.cursor / terminal.height).toLua() + ) + } + globals.addTwoArgFun("term.setCursor") { p0, p1 -> + terminal.setCursor(p0.checkint(), p1.checkint()) + LuaValue.NIL + } + globals.addZeroArgFun("term.getCursorBlink") { + terminal.blink.toLua() + } + globals.addOneArgFun("term.setCursorBlink") { p0 -> + terminal.blink = p0.checkboolean() + LuaValue.NIL + } + globals.addZeroArgFun("term.getTextColor") { + terminal.foreground.toLua() + } + globals.addZeroArgFun("term.getBackgroundColor") { + terminal.background.toLua() + } + globals.addOneArgFun("term.setTextColor") { p0 -> + terminal.foreground = p0.checkint() + LuaValue.NIL + } + globals.addOneArgFun("term.setBackgroundColor") { p0 -> + terminal.background = p0.checkint() + LuaValue.NIL + } + globals.addZeroArgFun("term.getSize") { + luaTableOf( + (terminal.width).toLua(), + (terminal.height).toLua() + ) + } + globals.addZeroArgFun("term.clear") { + terminal.clear() + LuaValue.NIL + } + globals.addZeroArgFun("term.clearLine") { + terminal.clearCurrentLine() + LuaValue.NIL + } + globals.addOneArgFun("term.scroll") { p0 -> + if (p0.checkint() < 0) + throw LuaError("Scroll amount must be a positive number") + terminal.scroll(p0.toint()) + LuaValue.NIL + } + globals.addOneArgFun("term.write") { p0 -> + terminal.print(p0.checkjstring()) + LuaValue.NIL + } + globals.addThreeArgFun("term.setText") { p0, p1, p2 -> + terminal.setOneText(p0.checkint(), p1.checkint(), p2.checkint().toByte()) + LuaValue.NIL + } + } + +} \ No newline at end of file diff --git a/src/net/torvald/terrarum/modulecomputers/virtualcomputer/computer/LuaComputerVM.kt b/src/net/torvald/terrarum/modulecomputers/virtualcomputer/computer/LuaComputerVM.kt index 2eac36095..c6a56c351 100644 --- a/src/net/torvald/terrarum/modulecomputers/virtualcomputer/computer/LuaComputerVM.kt +++ b/src/net/torvald/terrarum/modulecomputers/virtualcomputer/computer/LuaComputerVM.kt @@ -1,11 +1,17 @@ package net.torvald.terrarum.modulecomputers.virtualcomputer.computer import org.luaj.vm2.Globals +import org.luaj.vm2.LoadState import org.luaj.vm2.LuaValue -import org.luaj.vm2.lib.OneArgFunction -import org.luaj.vm2.lib.jse.JsePlatform +import org.luaj.vm2.compiler.LuaC +import org.luaj.vm2.lib.* +import org.luaj.vm2.lib.jse.JseBaseLib +import org.luaj.vm2.lib.jse.JseMathLib +import org.luaj.vm2.lib.jse.JseStringLib import java.io.InputStream + + /** * New plan: screw teletype and gui; only the simple 80*24 (size may mary) dumb terminal * @@ -13,7 +19,7 @@ import java.io.InputStream */ class LuaComputerVM(val display: MDA) { - val luaInstance: Globals = JsePlatform.standardGlobals() + val luaInstance: Globals// = JsePlatform.standardGlobals() val stdout = MDAPrintStream(display) val stderr = MDAPrintStream(display) @@ -21,6 +27,24 @@ class LuaComputerVM(val display: MDA) { init { + // initialise the lua instance + luaInstance = Globals() + luaInstance.load(JseBaseLib()) + luaInstance.load(PackageLib()) + luaInstance.load(Bit32Lib()) + luaInstance.load(TableLib()) + luaInstance.load(JseStringLib()) + luaInstance.load(CoroutineLib()) + luaInstance.load(JseMathLib()) + //luaInstance.load(JseIoLib()) + //luaInstance.load(JseOsLib()) + //luaInstance.load(LuajavaLib()) + + LoadTerrarumTermLib(luaInstance, display) + + LoadState.install(luaInstance) + LuaC.install(luaInstance) + // bit-bit32 alias luaInstance["bit"] = luaInstance["bit32"] @@ -29,26 +53,7 @@ class LuaComputerVM(val display: MDA) { luaInstance.STDERR = stderr luaInstance.STDIN = stdin - luaInstance.addOneArgFun("upgoer") { p0 -> - display.println("Up-goer ${p0.toint()} goes up!") - LuaValue.NIL - } - luaInstance.addOneArgFun("perkele.upgoer") { p0 -> - display.println("Up-goer ${p0.toint()} goes up!") - LuaValue.NIL - } - - luaInstance.addOneArgFun("perkele.saatana.jumalauta.vittu.upgoer") { p0 -> - display.println("Up-goer ${p0.toint()} goes up!") - LuaValue.NIL - } - - luaInstance.load("""print('Hello, world!') print('Ready.')""").invoke() - luaInstance.load("""print(upgoer)""").invoke() - luaInstance.load("""upgoer(1)""").invoke() - luaInstance.load("""perkele.upgoer(2)""").invoke() - luaInstance.load("""perkele.saatana.jumalauta.vittu.upgoer(5)""").invoke() } } @@ -100,7 +105,7 @@ fun Globals.addOneArgFun(identifier: String, function: (p0: LuaValue) -> LuaValu // actually put the function onto the target // for some reason, memoisation doesn't work here so we use recursion to reach the target table as generated above - fun putIntoTheTableRec(luaTable: LuaValue, recursionCount: Int) { + tailrec fun putIntoTheTableRec(luaTable: LuaValue, recursionCount: Int) { if (recursionCount == tableNames.lastIndex - 1) { luaTable[tableNames[tableNames.lastIndex]] = theActualFun } @@ -116,4 +121,173 @@ fun Globals.addOneArgFun(identifier: String, function: (p0: LuaValue) -> LuaValu } } -// don't add ZeroArgFun, TwoArgFun, ThreeArgFun until you make sure addOneArgFun to work! \ No newline at end of file +/** + * Install a function into the lua. + * @param identifier How you might call this lua function. E.g. "term.println" + */ +fun Globals.addZeroArgFun(identifier: String, function: () -> LuaValue) { + val theActualFun = object : ZeroArgFunction() { + override fun call(): LuaValue { + return function() + } + } + + val tableNames = identifier.split('.') + + if (tableNames.isEmpty()) throw IllegalArgumentException("Identifier is empty") + + //println(tableNames) + + if (this[tableNames[0]].isnil()) { + this[tableNames[0]] = LuaValue.tableOf() + } + else if (!this[tableNames[0]].istable()) { + throw IllegalStateException("Redefinition: '${tableNames[0]}' (${this[tableNames[0]]})") + } + + var currentTable = this[tableNames[0]] + + // turn nils into tables + if (tableNames.size > 1) { + tableNames.slice(1..tableNames.lastIndex).forEachIndexed { index, it -> + if (currentTable[it].isnil()) { + currentTable[it] = LuaValue.tableOf() + } + else if (!currentTable[it].istable()) { + throw IllegalStateException("Redefinition: '${tableNames.slice(0..(index + 1)).joinToString(".")}' (${currentTable[it]})") + } + + currentTable = currentTable[it] + } + + // actually put the function onto the target + // for some reason, memoisation doesn't work here so we use recursion to reach the target table as generated above + tailrec fun putIntoTheTableRec(luaTable: LuaValue, recursionCount: Int) { + if (recursionCount == tableNames.lastIndex - 1) { + luaTable[tableNames[tableNames.lastIndex]] = theActualFun + } + else { + putIntoTheTableRec(luaTable[tableNames[recursionCount + 1]], recursionCount + 1) + } + } + + putIntoTheTableRec(this[tableNames[0]], 0) + } + else { + this[tableNames[0]] = theActualFun + } +} + +/** + * Install a function into the lua. + * @param identifier How you might call this lua function. E.g. "term.println" + */ +fun Globals.addTwoArgFun(identifier: String, function: (p0: LuaValue, p1: LuaValue) -> LuaValue) { + val theActualFun = object : TwoArgFunction() { + override fun call(p0: LuaValue, p1: LuaValue): LuaValue { + return function(p0, p1) + } + } + + val tableNames = identifier.split('.') + + if (tableNames.isEmpty()) throw IllegalArgumentException("Identifier is empty") + + //println(tableNames) + + if (this[tableNames[0]].isnil()) { + this[tableNames[0]] = LuaValue.tableOf() + } + else if (!this[tableNames[0]].istable()) { + throw IllegalStateException("Redefinition: '${tableNames[0]}' (${this[tableNames[0]]})") + } + + var currentTable = this[tableNames[0]] + + // turn nils into tables + if (tableNames.size > 1) { + tableNames.slice(1..tableNames.lastIndex).forEachIndexed { index, it -> + if (currentTable[it].isnil()) { + currentTable[it] = LuaValue.tableOf() + } + else if (!currentTable[it].istable()) { + throw IllegalStateException("Redefinition: '${tableNames.slice(0..(index + 1)).joinToString(".")}' (${currentTable[it]})") + } + + currentTable = currentTable[it] + } + + // actually put the function onto the target + // for some reason, memoisation doesn't work here so we use recursion to reach the target table as generated above + tailrec fun putIntoTheTableRec(luaTable: LuaValue, recursionCount: Int) { + if (recursionCount == tableNames.lastIndex - 1) { + luaTable[tableNames[tableNames.lastIndex]] = theActualFun + } + else { + putIntoTheTableRec(luaTable[tableNames[recursionCount + 1]], recursionCount + 1) + } + } + + putIntoTheTableRec(this[tableNames[0]], 0) + } + else { + this[tableNames[0]] = theActualFun + } +} + +/** + * Install a function into the lua. + * @param identifier How you might call this lua function. E.g. "term.println" + */ +fun Globals.addThreeArgFun(identifier: String, function: (p0: LuaValue, p1: LuaValue, p2: LuaValue) -> LuaValue) { + val theActualFun = object : ThreeArgFunction() { + override fun call(p0: LuaValue, p1: LuaValue, p2: LuaValue): LuaValue { + return function(p0, p1, p2) + } + } + + val tableNames = identifier.split('.') + + if (tableNames.isEmpty()) throw IllegalArgumentException("Identifier is empty") + + //println(tableNames) + + if (this[tableNames[0]].isnil()) { + this[tableNames[0]] = LuaValue.tableOf() + } + else if (!this[tableNames[0]].istable()) { + throw IllegalStateException("Redefinition: '${tableNames[0]}' (${this[tableNames[0]]})") + } + + var currentTable = this[tableNames[0]] + + // turn nils into tables + if (tableNames.size > 1) { + tableNames.slice(1..tableNames.lastIndex).forEachIndexed { index, it -> + if (currentTable[it].isnil()) { + currentTable[it] = LuaValue.tableOf() + } + else if (!currentTable[it].istable()) { + throw IllegalStateException("Redefinition: '${tableNames.slice(0..(index + 1)).joinToString(".")}' (${currentTable[it]})") + } + + currentTable = currentTable[it] + } + + // actually put the function onto the target + // for some reason, memoisation doesn't work here so we use recursion to reach the target table as generated above + tailrec fun putIntoTheTableRec(luaTable: LuaValue, recursionCount: Int) { + if (recursionCount == tableNames.lastIndex - 1) { + luaTable[tableNames[tableNames.lastIndex]] = theActualFun + } + else { + putIntoTheTableRec(luaTable[tableNames[recursionCount + 1]], recursionCount + 1) + } + } + + putIntoTheTableRec(this[tableNames[0]], 0) + } + else { + this[tableNames[0]] = theActualFun + } +} diff --git a/src/net/torvald/terrarum/modulecomputers/virtualcomputer/computer/MDA.kt b/src/net/torvald/terrarum/modulecomputers/virtualcomputer/computer/MDA.kt index 4eae960db..a66170701 100644 --- a/src/net/torvald/terrarum/modulecomputers/virtualcomputer/computer/MDA.kt +++ b/src/net/torvald/terrarum/modulecomputers/virtualcomputer/computer/MDA.kt @@ -27,8 +27,8 @@ class MDA(val width: Int, val height: Int) { private val arrayElemOffset = 8L * if (AppLoader.is32BitJVM) 1 else 2 // 8 for 32-bit, 16 for 64-bit - private val glyphs = UnsafeHelper.allocate(width.toLong() * height) - private val attributes = UnsafeHelper.allocate(width.toLong() * height) + private val glyphs = UnsafeHelper.allocate(width.toLong() * height + 1) // extra one byte is absolutely needed + private val attributes = UnsafeHelper.allocate(width.toLong() * height + 1) var cursor = 0 private set @@ -37,8 +37,8 @@ class MDA(val width: Int, val height: Int) { var blink = true init { - glyphs.fillWith(0) - attributes.fillWith(1) + //glyphs.fillWith(0) + //attributes.fillWith(1) } /* @@ -116,10 +116,16 @@ class MDA(val width: Int, val height: Int) { /** Bulk write method. Any control characers will be represented as a glyph, rather than an actual control sequence. * E.g. '\n' will print a symbol. */ - fun setText(x: Int, y: Int, text: ByteArray) { + inline fun setText(x: Int, y: Int, text: ByteArray) { setText(x, y, text, toAttribute(background, foreground)) } + fun setOneText(x: Int, y: Int, text: Byte, attribute: Byte = toAttribute(background, foreground)) { + val o = wrapAround(x, y).toAddress() + glyphs[o] = text + attributes[o] = attribute + } + private fun setOneText(offset: Int, text: Byte, attribute: Byte = toAttribute(background, foreground)) { glyphs[offset.toLong()] = text attributes[offset.toLong()] = attribute @@ -131,7 +137,7 @@ class MDA(val width: Int, val height: Int) { print(text) write(0x0A) } - fun print(text: String) { + inline fun print(text: String) { print(text.toByteArray(charset)) } @@ -144,12 +150,11 @@ class MDA(val width: Int, val height: Int) { } fun write(text: Byte) { - when (text) { - // CR - 0x0D.toByte() -> { /* do nothing */ } // LF 0x0A.toByte() -> newline() + // all others (e.g. CR) + in 0x00.toByte()..0x0D.toByte() -> { /* do nothing */ } else -> { setOneText(cursor, text) @@ -163,6 +168,30 @@ class MDA(val width: Int, val height: Int) { } + fun clear() { + glyphs.fillWith(0) + attributes.fillWith(toAttribute(background, foreground)) + cursor = 0 + } + + fun clearCurrentLine() { + clearLine(cursor / width) + } + + fun clearLine(line: Int) { + val lineOffset = line * width + for (i in 0L until width) { + glyphs[lineOffset + i] = 0 + } + } + + fun clearLineAfterCursor() { + val lineOffset = (cursor / width) * width + for (i in (cursor % width).toLong() until width) { + glyphs[lineOffset + i] = 0 + } + } + /** * moves text and the current cursor position */ @@ -175,6 +204,8 @@ class MDA(val width: Int, val height: Int) { cursor -= offset.toInt() if (cursor < 0) cursor = 0 + + clearLineAfterCursor() } /** @@ -188,6 +219,7 @@ class MDA(val width: Int, val height: Int) { } cursor = (cursor / width) * width // set cursorX to 0 + clearLineAfterCursor() } fun dispose() { diff --git a/src/net/torvald/terrarum/modulecomputers/virtualcomputer/peripheral/CommLine.kt b/src/net/torvald/terrarum/modulecomputers/virtualcomputer/peripheral/CommLine.kt new file mode 100644 index 000000000..62670ef54 --- /dev/null +++ b/src/net/torvald/terrarum/modulecomputers/virtualcomputer/peripheral/CommLine.kt @@ -0,0 +1,37 @@ +package net.torvald.terrarum.modulecomputers.virtualcomputer.peripheral + +/** + * Abstraction of the communication line. (e.g. serial cable) + * + * A cable may have multiple of CommLines (e.g. serial cable need two for Tx and Rx) + * + * Created by minjaesong on 2019-07-10. + */ +open class CommLine(val bandwidth: Int) { + + open val postbox = StringBuilder() + + /** + * Returns how many bytes are actually posted, e.g. 0 when band limit is exceeded. + */ + open fun post(msg: String): Int { + if (bandwidth >= msg.length) { + postbox.append(msg) + return msg.length + } + else if (postbox.length >= bandwidth) { + return 0 + } + else { + postbox.append(msg.substring(0 until (bandwidth - postbox.length))) + return bandwidth - postbox.length + } + } + + open fun get(): String { + val s = postbox.toString() + postbox.clear() + return s + } + +} \ No newline at end of file diff --git a/src/net/torvald/terrarum/modulecomputers/virtualcomputer/standalone/StandaloneApp.kt b/src/net/torvald/terrarum/modulecomputers/virtualcomputer/standalone/StandaloneApp.kt index 17d358437..6fe0ea711 100644 --- a/src/net/torvald/terrarum/modulecomputers/virtualcomputer/standalone/StandaloneApp.kt +++ b/src/net/torvald/terrarum/modulecomputers/virtualcomputer/standalone/StandaloneApp.kt @@ -64,6 +64,7 @@ class StandaloneApp : Game() { Gdx.graphics.setTitle("Terrarum Lua Computer Standalone — F: ${Gdx.graphics.framesPerSecond}") //display.print(ByteArray(1){ (Math.random() * 255).toByte() }) + //display.print("@") batch.inUse { batch.color = Color.WHITE