mirror of
https://github.com/curioustorvald/tsvm.git
synced 2026-03-07 19:51:51 +09:00
can read mouse position via mmio
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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() }
|
||||
|
||||
@@ -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 = """
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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<Byte>(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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
192
src/net/torvald/util/CircularArray.kt
Normal file
192
src/net/torvald/util/CircularArray.kt
Normal file
@@ -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<T>(val size: Int, val overwriteOnOverflow: Boolean): Iterable<T> {
|
||||
|
||||
/**
|
||||
* 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<T> = arrayOfNulls<Any>(size) as Array<T>
|
||||
|
||||
/** 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<T> {
|
||||
if (isEmpty) {
|
||||
return object : Iterator<T> {
|
||||
override fun next(): T = throw EmptyStackException()
|
||||
override fun hasNext() = false
|
||||
}
|
||||
}
|
||||
|
||||
val rangeMax = getAbsoluteRange().last
|
||||
var counter = 0
|
||||
return object : Iterator<T> {
|
||||
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 <R> 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)
|
||||
}
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user