diff --git a/src/net/torvald/tsvm/GraphicsJSR223Delegate.kt b/src/net/torvald/tsvm/GraphicsJSR223Delegate.kt index f013e83..4a28852 100644 --- a/src/net/torvald/tsvm/GraphicsJSR223Delegate.kt +++ b/src/net/torvald/tsvm/GraphicsJSR223Delegate.kt @@ -39,6 +39,14 @@ class GraphicsJSR223Delegate(val vm: VM) { } } + fun plotPixel(x: Int, y: Int, color: Byte) { + getFirstGPU()?.let { + if (x in 0 until GraphicsAdapter.WIDTH && y in 0 until GraphicsAdapter.HEIGHT) { + it.poke(y.toLong() * GraphicsAdapter.WIDTH + x, color) + } + } + } + private fun GraphicsAdapter._loadbulk(fromAddr: Int, toAddr: Int, length: Int) { UnsafeHelper.memcpy( vm.usermem.ptr + fromAddr, diff --git a/src/net/torvald/tsvm/VM.kt b/src/net/torvald/tsvm/VM.kt index 278888d..3019cbf 100644 --- a/src/net/torvald/tsvm/VM.kt +++ b/src/net/torvald/tsvm/VM.kt @@ -66,6 +66,8 @@ class VM( val peripheralTable = Array(8) { PeripheralEntry() } + internal fun getIO(): IOSpace = peripheralTable[0].peripheral as IOSpace + lateinit var printStream: OutputStream lateinit var errorStream: OutputStream lateinit var inputStream: InputStream @@ -73,7 +75,7 @@ class VM( init { peripheralTable[0] = PeripheralEntry( "io", - IOSpace(), + IOSpace(this), HW_RESERVE_SIZE, MMIO_SIZE.toInt() - 256, 64 @@ -90,6 +92,10 @@ class VM( return null } + fun update(delta: Float) { + getIO().update(delta) + } + fun dispose() { usermem.destroy() peripheralTable.forEach { it.peripheral?.dispose() } diff --git a/src/net/torvald/tsvm/VMGUI.kt b/src/net/torvald/tsvm/VMGUI.kt index 1392715..934c42f 100644 --- a/src/net/torvald/tsvm/VMGUI.kt +++ b/src/net/torvald/tsvm/VMGUI.kt @@ -2,11 +2,13 @@ package net.torvald.tsvm import com.badlogic.gdx.ApplicationAdapter import com.badlogic.gdx.Gdx +import com.badlogic.gdx.InputProcessor import com.badlogic.gdx.backends.lwjgl.LwjglApplicationConfiguration import com.badlogic.gdx.graphics.OrthographicCamera import com.badlogic.gdx.graphics.g2d.SpriteBatch import kotlinx.coroutines.* import net.torvald.tsvm.peripheral.GraphicsAdapter +import net.torvald.tsvm.peripheral.IOSpace import java.io.InputStream import java.io.OutputStream @@ -25,7 +27,7 @@ class VMGUI(val appConfig: LwjglApplicationConfiguration) : ApplicationAdapter() override fun create() { super.create() - gpu = GraphicsAdapter(lcdMode = true) + gpu = GraphicsAdapter(vm, lcdMode = false) vm.peripheralTable[1] = PeripheralEntry( VM.PERITYPE_TERM, @@ -49,8 +51,11 @@ class VMGUI(val appConfig: LwjglApplicationConfiguration) : ApplicationAdapter() // TEST PRG vmRunner = VMRunnerFactory(vm, "js") coroutineJob = GlobalScope.launch { - vmRunner.executeCommand(sanitiseJS(gpuTestPaletteJs)) + vmRunner.executeCommand(sanitiseJS(shitcode)) } + + + Gdx.input.inputProcessor = vm.getIO() } private var updateAkku = 0.0 @@ -77,16 +82,11 @@ class VMGUI(val appConfig: LwjglApplicationConfiguration) : ApplicationAdapter() private var latch = true private fun updateGame(delta: Float) { - - + vm.update(delta) } fun poke(addr: Long, value: Byte) = vm.poke(addr, value) - private fun paintTestPalette() { - - } - private val gpuTestPaletteKt = """ val w = 560 val h = 448 @@ -265,6 +265,13 @@ println("Starting TVDOS..."); println("TSVM Disk Operating System, version 1.20"); println(""); print("C:\\\\>"); + +while (true) { + var mx = vm.peek(-33) + vm.peek(-34) * 256; + var my = vm.peek(-35) + vm.peek(-36) * 256; + println("mx: "+mx+", my: "+my); + graphics.plotPixel(mx, my, (mx + my) % 255); +} """.trimIndent() private val gpuTestPaletteJava = """ diff --git a/src/net/torvald/tsvm/VMJSR223Delegate.kt b/src/net/torvald/tsvm/VMJSR223Delegate.kt index 841240b..5aad572 100644 --- a/src/net/torvald/tsvm/VMJSR223Delegate.kt +++ b/src/net/torvald/tsvm/VMJSR223Delegate.kt @@ -8,7 +8,7 @@ import net.torvald.tsvm.peripheral.GraphicsAdapter class VMJSR223Delegate(val vm: VM) { fun poke(addr: Int, value: Int) = vm.poke(addr.toLong(), value.toByte()) - fun peek(addr: Int) = vm.peek(addr.toLong()) + fun peek(addr: Int) = vm.peek(addr.toLong())!!.toInt().and(255) fun nanoTime() = System.nanoTime() fun malloc(size: Int) = vm.malloc(size) fun free(ptr: Int) = vm.free(ptr) diff --git a/src/net/torvald/tsvm/peripheral/GlassTty.kt b/src/net/torvald/tsvm/peripheral/GlassTty.kt index e5028e0..1d23e2f 100644 --- a/src/net/torvald/tsvm/peripheral/GlassTty.kt +++ b/src/net/torvald/tsvm/peripheral/GlassTty.kt @@ -204,8 +204,6 @@ 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) @@ -247,6 +245,17 @@ abstract class GlassTty(val TEXT_ROWS: Int, val TEXT_COLS: Int) { INITIAL, ESC, CSI, NUM1, SEP1, NUM2, SEP2, NUM3 } + + /** + * Puts a key into a keyboard buffer + */ + abstract fun putKey(key: Int) + + /** + * Takes a key from a keyboard buffer + */ + abstract fun takeKey(): Int + } /* diff --git a/src/net/torvald/tsvm/peripheral/GraphicsAdapter.kt b/src/net/torvald/tsvm/peripheral/GraphicsAdapter.kt index 732c371..b0b62b1 100644 --- a/src/net/torvald/tsvm/peripheral/GraphicsAdapter.kt +++ b/src/net/torvald/tsvm/peripheral/GraphicsAdapter.kt @@ -8,13 +8,18 @@ import net.torvald.UnsafeHelper import net.torvald.tsvm.AppLoader import net.torvald.tsvm.VM import net.torvald.tsvm.kB +import net.torvald.util.CircularArray 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 { +class GraphicsAdapter(val vm: VM, val lcdMode: Boolean = false) : GlassTty(Companion.TEXT_ROWS, Companion.TEXT_COLS), PeriBase { + + override fun getVM(): VM { + return vm + } internal val framebuffer = Pixmap(WIDTH, HEIGHT, Pixmap.Format.Alpha) private var rendertex = Texture(1, 1, Pixmap.Format.RGBA8888) @@ -569,6 +574,16 @@ class GraphicsAdapter(val lcdMode: Boolean = false) : GlassTty(Companion.TEXT_RO } } + override fun putKey(key: Int) { + vm.poke(-39, key.toByte()) + } + + /** + * @return key code in 0..255 (TODO: JInput Keycode or ASCII-Code?) + */ + override fun takeKey(): Int { + return vm.peek(-38)!!.toInt().and(255) + } private fun Boolean.toInt() = if (this) 1 else 0 diff --git a/src/net/torvald/tsvm/peripheral/IOSpace.kt b/src/net/torvald/tsvm/peripheral/IOSpace.kt index f7409cd..9f6a968 100644 --- a/src/net/torvald/tsvm/peripheral/IOSpace.kt +++ b/src/net/torvald/tsvm/peripheral/IOSpace.kt @@ -1,22 +1,94 @@ package net.torvald.tsvm.peripheral -class IOSpace : PeriBase { +import com.badlogic.gdx.Gdx +import com.badlogic.gdx.InputProcessor +import net.torvald.UnsafeHelper +import net.torvald.tsvm.VM +import net.torvald.util.CircularArray + +class IOSpace(val vm: VM) : PeriBase, InputProcessor { + + override fun getVM(): VM { + return vm + } + + /** Absolute x-position of the computer GUI */ + var guiPosX = 0 + /** Absolute y-position of the computer GUI */ + var guiPosY = 0 + + private val keyboardBuffer = CircularArray(32, true) + private var mouseX: Short = 0 + private var mouseY: Short = 0 + private var mouseDown = false + override fun peek(addr: Long): Byte? { - TODO("Not yet implemented") + return mmio_read(addr) } override fun poke(addr: Long, byte: Byte) { - TODO("Not yet implemented") + mmio_write(addr, byte) } override fun mmio_read(addr: Long): Byte? { - TODO("Not yet implemented") + val adi = addr.toInt() + return when (addr) { + in 0..31 -> keyboardBuffer[(addr.toInt())] ?: -1 + in 32..33 -> (mouseX.toInt() shr (adi - 32).times(8)).toByte() + in 34..35 -> (mouseY.toInt() shr (adi - 34).times(8)).toByte() + 36L -> if (mouseDown) 1 else 0 + 37L -> keyboardBuffer.removeHead() ?: -1 + else -> -1 + } } override fun mmio_write(addr: Long, byte: Byte) { - TODO("Not yet implemented") + val adi = addr.toInt() + val bi = byte.toInt().and(255) + when (addr) { + } } override fun dispose() { } + + fun update(delta: Float) { + mouseX = (Gdx.input.x + guiPosX).toShort() + mouseY = (Gdx.input.y + guiPosY).toShort() + mouseDown = Gdx.input.isTouched + } + + override fun touchUp(p0: Int, p1: Int, p2: Int, p3: Int): Boolean { + return false + } + + override fun mouseMoved(p0: Int, p1: Int): Boolean { + return false + } + + override fun keyTyped(p0: Char): Boolean { + keyboardBuffer.appendTail(p0.toByte()) + println("[IO] Key typed: $p0") + return true + } + + override fun scrolled(p0: Int): Boolean { + return false + } + + override fun keyUp(p0: Int): Boolean { + return false + } + + override fun touchDragged(p0: Int, p1: Int, p2: Int): Boolean { + return false + } + + override fun keyDown(p0: Int): Boolean { + return false + } + + override fun touchDown(p0: Int, p1: Int, p2: Int, p3: Int): Boolean { + return false + } } \ No newline at end of file diff --git a/src/net/torvald/tsvm/peripheral/PeriBase.kt b/src/net/torvald/tsvm/peripheral/PeriBase.kt index 504b0f0..5cb030b 100644 --- a/src/net/torvald/tsvm/peripheral/PeriBase.kt +++ b/src/net/torvald/tsvm/peripheral/PeriBase.kt @@ -1,5 +1,7 @@ package net.torvald.tsvm.peripheral +import net.torvald.tsvm.VM + interface PeriBase { /** @@ -16,4 +18,6 @@ interface PeriBase { fun mmio_write(addr: Long, byte: Byte) fun dispose() + + fun getVM(): VM } \ No newline at end of file diff --git a/src/net/torvald/util/CircularArray.kt b/src/net/torvald/util/CircularArray.kt new file mode 100644 index 0000000..145ec0a --- /dev/null +++ b/src/net/torvald/util/CircularArray.kt @@ -0,0 +1,192 @@ +package net.torvald.util + +import java.util.* + + +/** + * buffer[head] contains the most recent item, whereas buffer[tail] contains the oldest one. + * + * Notes for particle storage: + * Particles does not need to be removed, just let it overwrite as their operation is rather + * lightweight. So, just flagDespawn = true if it need to be "deleted" so that it won't update + * anymore. + * + * Created by minjaesong on 2017-01-22. + */ +class CircularArray(val size: Int, val overwriteOnOverflow: Boolean): Iterable { + + /** + * What to do RIGHT BEFORE old element is being overridden by the new element (only makes sense when ```overwriteOnOverflow = true```) + * + * This function will not be called when ```removeHead()``` or ```removeTail()``` is called. + */ + var overwritingPolicy: (T) -> Unit = { + // do nothing + } + + val buffer: Array = arrayOfNulls(size) as Array + + /** Tail stands for the oldest element. The tail index points AT the tail element */ + var tail: Int = 0; private set + /** Head stands for the youngest element. The head index points AFTER the head element */ + var head: Int = 0; private set + + private var overflow = false + + val lastIndex = size - 1 + + /** + * Number of elements that forEach() or fold() would iterate. + */ + val elemCount: Int + get() = if (overflow) size else head - tail + val isEmpty: Boolean + get() = !overflow && head == tail + + private inline fun incHead() { head = (head + 1).wrap() } + private inline fun decHead() { head = (head - 1).wrap() } + private inline fun incTail() { tail = (tail + 1).wrap() } + private inline fun decTail() { tail = (tail - 1).wrap() } + + fun clear() { + tail = 0 + head = 0 + overflow = false + } + + /** + * When the overflowing is enabled, tail element (ultimate element) will be changed into the penultimate element. + */ + fun appendHead(item: T) { + if (overflow && !overwriteOnOverflow) { + throw StackOverflowError() + } + else { + if (overflow) { + overwritingPolicy.invoke(buffer[head]) + } + + buffer[head] = item + incHead() + } + + if (overflow) { + incTail() + } + + // must be checked AFTER the actual head increment; otherwise this condition doesn't make sense + if (tail == head) { + overflow = true + } + } + + fun appendTail(item: T) { + // even if overflowing is enabled, appending at tail causes head element to be altered, therefore such action + // must be blocked by throwing overflow error + + if (overflow) { + throw StackOverflowError() + } + else { + decTail() + buffer[tail] = item + } + + // must be checked AFTER the actual head increment; otherwise this condition doesn't make sense + if (tail == head) { + overflow = true + } + } + + fun removeHead(): T? { + if (isEmpty) return null + + decHead() + overflow = false + + return buffer[head] + } + + fun removeTail(): T? { + if (isEmpty) return null + + val ret = buffer[tail] + incTail() + overflow = false + + return ret + } + + /** Returns the youngest (last of the array) element */ + fun getHeadElem(): T? = if (isEmpty) null else buffer[(head - 1).wrap()] + /** Returns the oldest (first of the array) element */ + fun getTailElem(): T? = if (isEmpty) null else buffer[tail] + + /** + * Relative-indexed get. Index of zero will return the head element. + */ + operator fun get(index: Int): T? = buffer[(head - 1 - index).wrap()] + + private fun getAbsoluteRange() = 0 until when { + head == tail -> buffer.size + tail > head -> buffer.size - (((head - 1).wrap()) - tail) + else -> head - tail + } + + override fun iterator(): Iterator { + if (isEmpty) { + return object : Iterator { + override fun next(): T = throw EmptyStackException() + override fun hasNext() = false + } + } + + val rangeMax = getAbsoluteRange().last + var counter = 0 + return object : Iterator { + override fun next(): T { + val ret = buffer[(counter + tail).wrap()] + counter += 1 + return ret + } + + override fun hasNext() = (counter <= rangeMax) + } + } + + /** + * Iterates the array with oldest element (tail) first. + */ + fun forEach(action: (T) -> Unit) { + // for (element in buffer) action(element) + // return nothing + + iterator().forEach(action) + } + + fun fold(initial: R, operation: (R, T) -> R): R { + // accumulator = initial + // for (element in buffer) accumulator = operation(accumulator, element) + // return accumulator + + var accumulator = initial + + if (isEmpty) + return initial + else { + iterator().forEach { + accumulator = operation(accumulator, it) + } + } + + return accumulator + } + + private inline fun Int.wrap() = this fmod size + + override fun toString(): String { + return "CircularArray(size=${buffer.size}, head=$head, tail=$tail, overflow=$overflow)" + } + + private inline infix fun Int.fmod(other: Int) = Math.floorMod(this, other) +} \ No newline at end of file diff --git a/terranmon.txt b/terranmon.txt index da12a99..eb09602 100644 --- a/terranmon.txt +++ b/terranmon.txt @@ -37,6 +37,23 @@ User area: 8 MB, hardware area: 8 MB -------------------------------------------------------------------------------- +IO Device + +Endianness: little + +MMIO + +0..31: Raw Keyboard Buffer read. Won't shift the key buffer +32..33: Mouse X pos +34..35: Mouse Y pos +36: Mouse down? (1 for TRUE, 0 for FALSE) +37: Read/Write single key input. Key buffer will be shifted. Manual writing is + usually unnecessary as such action must be automatically managed via LibGDX + input processing. + + +-------------------------------------------------------------------------------- + VRAM Bank 0 (256 kB) Endianness: little