diff --git a/src/net/torvald/tsvm/GraphicsJSR223Delegate.kt b/src/net/torvald/tsvm/GraphicsJSR223Delegate.kt index e8cb045..f013e83 100644 --- a/src/net/torvald/tsvm/GraphicsJSR223Delegate.kt +++ b/src/net/torvald/tsvm/GraphicsJSR223Delegate.kt @@ -7,7 +7,7 @@ import sun.nio.ch.DirectBuffer class GraphicsJSR223Delegate(val vm: VM) { private fun getFirstGPU(): GraphicsAdapter? { - return vm.peripheralTable[vm.findPeribyType("gpu") ?: return null].peripheral as? GraphicsAdapter + return vm.findPeribyType(VM.PERITYPE_TERM)?.peripheral as? GraphicsAdapter } fun resetPalette() { diff --git a/src/net/torvald/tsvm/VM.kt b/src/net/torvald/tsvm/VM.kt index 8a718e7..278888d 100644 --- a/src/net/torvald/tsvm/VM.kt +++ b/src/net/torvald/tsvm/VM.kt @@ -7,6 +7,8 @@ import net.torvald.tsvm.firmware.Firmware.Companion.toLuaValue import net.torvald.tsvm.peripheral.IOSpace import net.torvald.tsvm.peripheral.PeriBase import org.luaj.vm2.LuaValue +import java.io.InputStream +import java.io.OutputStream import java.util.* import kotlin.math.ceil import kotlin.random.Random @@ -64,6 +66,10 @@ class VM( val peripheralTable = Array(8) { PeripheralEntry() } + lateinit var printStream: OutputStream + lateinit var errorStream: OutputStream + lateinit var inputStream: InputStream + init { peripheralTable[0] = PeripheralEntry( "io", @@ -77,9 +83,9 @@ class VM( } - fun findPeribyType(searchTerm: String): Int? { + fun findPeribyType(searchTerm: String): PeripheralEntry? { for (i in 0..7) { - if (peripheralTable[i].type == searchTerm) return i + if (peripheralTable[i].type == searchTerm) return peripheralTable[i] } return null } @@ -100,7 +106,7 @@ class VM( val HW_RESERVE_SIZE = 1024.kB() val USER_SPACE_SIZE = 8192.kB() - const val PERITYPE_GRAPHICS = "gpu" + const val PERITYPE_TERM = "gpu" } internal fun translateAddr(addr: Long): Pair { diff --git a/src/net/torvald/tsvm/VMGUI.kt b/src/net/torvald/tsvm/VMGUI.kt index 9eef719..78606d0 100644 --- a/src/net/torvald/tsvm/VMGUI.kt +++ b/src/net/torvald/tsvm/VMGUI.kt @@ -7,8 +7,8 @@ import com.badlogic.gdx.graphics.OrthographicCamera import com.badlogic.gdx.graphics.g2d.SpriteBatch import kotlinx.coroutines.* import net.torvald.tsvm.peripheral.GraphicsAdapter -import kotlin.coroutines.CoroutineContext -import kotlin.coroutines.suspendCoroutine +import java.io.InputStream +import java.io.OutputStream class VMGUI(val appConfig: LwjglApplicationConfiguration) : ApplicationAdapter() { @@ -28,9 +28,9 @@ class VMGUI(val appConfig: LwjglApplicationConfiguration) : ApplicationAdapter() gpu = GraphicsAdapter(lcdMode = false) vm.peripheralTable[1] = PeripheralEntry( - VM.PERITYPE_GRAPHICS, + VM.PERITYPE_TERM, gpu, - 256.kB(), + GraphicsAdapter.VRAM_SIZE, 16, 0 ) @@ -42,6 +42,9 @@ class VMGUI(val appConfig: LwjglApplicationConfiguration) : ApplicationAdapter() batch.projectionMatrix = camera.combined Gdx.gl20.glViewport(0, 0, appConfig.width, appConfig.height) + vm.printStream = gpu.getPrintStream() + vm.errorStream = gpu.getErrorStream() + //inputStream = gpu.getInputStream() // TEST PRG vmRunner = VMRunnerFactory(vm, "js") @@ -244,7 +247,7 @@ while (true) { for (var k = 0; k < 2560; k++) { vm.poke(-(253952 + k + 1) - hwoff, -2); // transparent vm.poke(-(253952 + 2560 + k + 1) - hwoff, -1); // white - vm.poke(-(253952 + 2560*2 + k + 1) - hwoff, Math.round(Math.random() * 255)); + /*vm.poke(-(253952 + 2560*2 + k + 1) - hwoff, Math.round(Math.random() * 255));*/ } rng = inthash(rng); @@ -255,7 +258,7 @@ while (true) { } """.trimIndent() - private val gpuTestPaletteJs = "eval('${jscode.replace(Regex("//[^\\n]*"), "").replace('\n', ' ')}')" + private val gpuTestPaletteJs = "function print(s){vm.print(s)}eval('${jscode.replace(Regex("//[^\\n]*"), "").replace('\n', ' ')}')" private val gpuTestPaletteJava = """ diff --git a/src/net/torvald/tsvm/VMJSR223Delegate.kt b/src/net/torvald/tsvm/VMJSR223Delegate.kt index 7da90c4..7848c9d 100644 --- a/src/net/torvald/tsvm/VMJSR223Delegate.kt +++ b/src/net/torvald/tsvm/VMJSR223Delegate.kt @@ -13,4 +13,13 @@ class VMJSR223Delegate(val vm: VM) { fun malloc(size: Int) = vm.malloc(size) fun free(ptr: Int) = vm.free(ptr) + fun print(s: String) { + //println("[Nashorn] $s") + vm.printStream.write((s + '\n').toByteArray()) + } + +} + +class VMSerialDebugger(val vm: VM) { + fun print(s: String) = System.out.println(s) } \ No newline at end of file diff --git a/src/net/torvald/tsvm/VMRunnerFactory.kt b/src/net/torvald/tsvm/VMRunnerFactory.kt index 0dab64c..a657dbb 100644 --- a/src/net/torvald/tsvm/VMRunnerFactory.kt +++ b/src/net/torvald/tsvm/VMRunnerFactory.kt @@ -52,6 +52,7 @@ object VMRunnerFactory { bind.put("graphics", GraphicsJSR223Delegate(vm)) //bind.put("poke", { a: Long, b: Byte -> vm.poke(a, b) }) // kts: lambda does not work... //bind.put("nanotime", { System.nanoTime() }) + bind.put("serial", VMSerialDebugger(vm)) } override suspend fun executeCommand(command: String) { diff --git a/src/net/torvald/tsvm/peripheral/GlassTty.kt b/src/net/torvald/tsvm/peripheral/GlassTty.kt index fe82d5c..b9c3716 100644 --- a/src/net/torvald/tsvm/peripheral/GlassTty.kt +++ b/src/net/torvald/tsvm/peripheral/GlassTty.kt @@ -1,13 +1,27 @@ package net.torvald.tsvm.peripheral +import java.io.InputStream +import java.io.OutputStream import java.util.* /** * Implements standard TTY that can interpret some of the ANSI escape sequences + * + * A paper tty must be able to implemented by extending this class (and butchering some of the features), of which it + * sets limits on some of the functions (notably 'setCursorPos') */ abstract class GlassTty(val TEXT_ROWS: Int, val TEXT_COLS: Int) { + /** + * (x, y) + */ abstract fun getCursorPos(): Pair + + /** + * Think of it as a real paper tty; + * setCursorPos must "wrap" the cursor properly when x-value goes out of screen bound. + * For y-value, only when y < 0, set y to zero and don't care about the y-value goes out of bound. + */ abstract fun setCursorPos(x: Int, y: Int) abstract var rawCursorPos: Int @@ -19,6 +33,23 @@ abstract class GlassTty(val TEXT_ROWS: Int, val TEXT_COLS: Int) { abstract fun putChar(x: Int, y: Int, text: Byte, foreColour: Byte = ttyFore.toByte(), backColour: Byte = ttyBack.toByte()) + fun writeOut(char: Byte) { + val printable = acceptChar(char) + + if (printable) { + val (x, y) = getCursorPos() + putChar(x, y, char) + setCursorPos(x + 1, y) // should automatically wrap and advance a line for out-of-bound x-value + } + + // deal with y-axis out-of-bounds + val (cx, cy) = getCursorPos() + if (cy >= TEXT_ROWS) { + scrollUp(cy - TEXT_ROWS + 1) + setCursorPos(cx, TEXT_ROWS - 1) + } + } + private var ttyEscState = TTY_ESC_STATE.INITIAL private val ttyEscArguments = Stack() /** @@ -52,11 +83,14 @@ abstract class GlassTty(val TEXT_ROWS: Int, val TEXT_COLS: Int) { when (ttyEscState) { TTY_ESC_STATE.INITIAL -> { - if (char == ESC) { - ttyEscState = TTY_ESC_STATE.ESC - } - else { - return true + when (char) { + ESC -> ttyEscState = TTY_ESC_STATE.ESC + LF -> crlf() + BS -> backspace() + TAB -> insertTab() + BEL -> ringBell() + in 0x00.toByte()..0x1F.toByte() -> return false + else -> return true } } TTY_ESC_STATE.ESC -> { @@ -170,6 +204,8 @@ abstract class GlassTty(val TEXT_ROWS: Int, val TEXT_COLS: Int) { return false } + + abstract fun resetTtyStatus() abstract fun cursorUp(arg: Int = 1) abstract fun cursorDown(arg: Int = 1) @@ -180,15 +216,31 @@ abstract class GlassTty(val TEXT_ROWS: Int, val TEXT_COLS: Int) { abstract fun cursorX(arg: Int = 1) // aka Cursor Horizintal Absolute abstract fun eraseInDisp(arg: Int = 0) abstract fun eraseInLine(arg: Int = 0) + /** New lines are added at the bottom */ abstract fun scrollUp(arg: Int = 1) + /** New lines are added at the top */ abstract fun scrollDown(arg: Int = 1) abstract fun sgrOneArg(arg: Int = 0) abstract fun sgrTwoArg(arg1: Int, arg2: Int) abstract fun sgrThreeArg(arg1: Int, arg2: Int, arg3: Int) + /** The values are one-based + * @param arg1 y-position (row) + * @param arg2 x-position (column) */ abstract fun cursorXY(arg1: Int, arg2: Int) abstract fun ringBell() abstract fun insertTab() + abstract fun crlf() + abstract fun backspace() + abstract fun getPrintStream(): OutputStream + abstract fun getErrorStream(): OutputStream + abstract fun getInputStream(): InputStream + + private val CR = 0x0D.toByte() + private val LF = 0x0A.toByte() + private val TAB = 0x09.toByte() + private val BS = 0x08.toByte() + private val BEL = 0x07.toByte() private val ESC = 0x1B.toByte() private enum class TTY_ESC_STATE { diff --git a/src/net/torvald/tsvm/peripheral/GraphicsAdapter.kt b/src/net/torvald/tsvm/peripheral/GraphicsAdapter.kt index d210e9b..a53332a 100644 --- a/src/net/torvald/tsvm/peripheral/GraphicsAdapter.kt +++ b/src/net/torvald/tsvm/peripheral/GraphicsAdapter.kt @@ -9,6 +9,9 @@ import net.torvald.tsvm.AppLoader import net.torvald.tsvm.VM import net.torvald.tsvm.kB import sun.nio.ch.DirectBuffer +import java.io.InputStream +import java.io.OutputStream +import java.io.PrintStream import kotlin.experimental.and class GraphicsAdapter(val lcdMode: Boolean = false) : GlassTty(Companion.TEXT_ROWS, Companion.TEXT_COLS), PeriBase { @@ -58,8 +61,28 @@ class GraphicsAdapter(val lcdMode: Boolean = false) : GlassTty(Companion.TEXT_RO set(value) { spriteAndTextArea.setShort(memTextCursorPosOffset, value.toShort()) } override fun getCursorPos() = rawCursorPos % TEXT_COLS to rawCursorPos / TEXT_COLS + /** + * Think of it as a real paper tty; + * setCursorPos must "wrap" the cursor properly when x-value goes out of screen bound. + * For y-value, only when y < 0, set y to zero and don't care about the y-value goes out of bound. + */ override fun setCursorPos(x: Int, y: Int) { - rawCursorPos = toTtyTextOffset(x, y) + var newx = x + var newy = y + + if (newx >= TEXT_COLS) { + newx = 0 + newy += 1 + } + else if (newx < 0) { + newx = 0 + } + + if (newy < 0) { + newy = 0 // DON'T SCROLL when cursor goes ABOVE the screen + } + + rawCursorPos = toTtyTextOffset(newx, newy) } private fun toTtyTextOffset(x: Int, y: Int) = y * TEXT_COLS + x @@ -80,6 +103,7 @@ class GraphicsAdapter(val lcdMode: Boolean = false) : GlassTty(Companion.TEXT_RO // -1 is preferred because it points to the colour CLEAR, and it's constant. spriteAndTextArea.fillWith(-1) + setCursorPos(0, 0) println(framebuffer.pixels.limit()) } @@ -194,6 +218,157 @@ class GraphicsAdapter(val lcdMode: Boolean = false) : GlassTty(Companion.TEXT_RO spriteAndTextArea[memTextOffset + textOff] = text } + override fun cursorUp(arg: Int) { + val (x, y) = getCursorPos() + setCursorPos(x, y - arg) + } + + override fun cursorDown(arg: Int) { + val (x, y) = getCursorPos() + val newy = y + arg + setCursorPos(x, if (newy >= TEXT_ROWS) TEXT_ROWS - 1 else newy) + } + + override fun cursorFwd(arg: Int) { + val (x, y) = getCursorPos() + setCursorPos(x + arg, y) + } + + override fun cursorBack(arg: Int) { + val (x, y) = getCursorPos() + setCursorPos(x - arg, y) + } + + override fun cursorNextLine(arg: Int) { + val (_, y) = getCursorPos() + val newy = y + arg + setCursorPos(0, if (newy >= TEXT_ROWS) TEXT_ROWS - 1 else newy) + if (newy >= TEXT_ROWS) { + scrollUp(newy - TEXT_ROWS + 1) + } + } + + override fun cursorPrevLine(arg: Int) { + val (_, y) = getCursorPos() + setCursorPos(0, y - arg) + } + + override fun cursorX(arg: Int) { + val (_, y) = getCursorPos() + setCursorPos(arg, y) + } + + override fun eraseInDisp(arg: Int) { + when (arg) { + else -> TODO() + } + } + + override fun eraseInLine(arg: Int) { + when (arg) { + else -> TODO() + } } + + /** New lines are added at the bottom */ + override fun scrollUp(arg: Int) { + //TODO("Not yet implemented") + } + + /** New lines are added at the top */ + override fun scrollDown(arg: Int) { + TODO("Not yet implemented") + } + + override fun sgrOneArg(arg: Int) { + TODO("Not yet implemented") + } + + override fun sgrTwoArg(arg1: Int, arg2: Int) { + TODO("Not yet implemented") + } + + override fun sgrThreeArg(arg1: Int, arg2: Int, arg3: Int) { + TODO("Not yet implemented") + } + + /** The values are one-based + * @param arg1 y-position (row) + * @param arg2 x-position (column) */ + override fun cursorXY(arg1: Int, arg2: Int) { + TODO("Not yet implemented") + } + + override fun ringBell() { + TODO("Not yet implemented") + } + + override fun insertTab() { + TODO("Not yet implemented") + } + + override fun crlf() { + val (_, y) = getCursorPos() + val newy = y + 1 + setCursorPos(0, if (newy >= TEXT_ROWS) TEXT_ROWS - 1 else newy) + if (newy >= TEXT_ROWS) scrollUp(1) + } + + override fun backspace() { + val (x, y) = getCursorPos() + putChar(x, y, 0x20.toByte()) + setCursorPos(x - 1, y) + } + + private lateinit var PRINTSTREAM_INSTANCE: OutputStream + private lateinit var ERRORSTREAM_INSTANCE: OutputStream + private lateinit var INPUTSTREAM_INSTANCE: InputStream + + override fun getPrintStream(): OutputStream { + try { + return PRINTSTREAM_INSTANCE + } + catch (e: UninitializedPropertyAccessException) { + PRINTSTREAM_INSTANCE = object : OutputStream() { + override fun write(p0: Int) { + writeOut(p0.toByte()) + } + } + + return PRINTSTREAM_INSTANCE + } + + } + + override fun getErrorStream(): OutputStream { + try { + return ERRORSTREAM_INSTANCE + } + catch (e: UninitializedPropertyAccessException) { + ERRORSTREAM_INSTANCE = object : OutputStream() { + private val SGI_RED = byteArrayOf(0x1B, 0x5B, 0x33, 0x31, 0x6D) + private val SGI_RESET = byteArrayOf(0x1B, 0x5B, 0x6D) + + override fun write(p0: Int) { + SGI_RED.forEach { writeOut(it) } + writeOut(p0.toByte()) + SGI_RESET.forEach { writeOut(it) } + } + + override fun write(p0: ByteArray) { + SGI_RED.forEach { writeOut(it) } + p0.forEach { writeOut(it) } + SGI_RESET.forEach { writeOut(it) } + } + } + + return ERRORSTREAM_INSTANCE + } + } + + override fun getInputStream(): InputStream { + TODO("Not yet implemented") + } + override fun dispose() { framebuffer.dispose() rendertex.dispose()