codes split into modules: tsvm_core, tsvm_executable, TerranBASICexecutable

This commit is contained in:
minjaesong
2021-12-03 11:57:31 +09:00
parent 20b34c8d19
commit 5e290061f4
49 changed files with 582 additions and 281 deletions

View File

@@ -0,0 +1,133 @@
package net.torvald.terrarum.modulecomputers.tsvmperipheral
import com.badlogic.gdx.files.FileHandle
import com.badlogic.gdx.graphics.Pixmap
import net.torvald.tsvm.VM
import net.torvald.tsvm.peripheral.BlockTransferInterface
import net.torvald.tsvm.peripheral.TestDiskDrive
import net.torvald.tsvm.peripheral.trimNull
import java.io.ByteArrayOutputStream
/**
* Created by minjaesong on 2021-12-02.
*/
class WorldRadar(pngfile: FileHandle) : BlockTransferInterface(false, true) {
private val W = 162
private val H = 142
private val world = ByteArray(256*256)
private val AIR = 0.toByte()
private val DIRT = 1.toByte()
private val GRASS = 2.toByte()
private val STONE = 16.toByte()
private val AIR_OUT = 0.toByte()
private val GRASS_OUT = 2.toByte()
private val DIRT_OUT = 4.toByte()
private val STONE_OUT = 7.toByte()
init {
statusCode = TestDiskDrive.STATE_CODE_STANDBY
val worldTex = Pixmap(pngfile)
for (y in 0 until worldTex.height) { for (x in 0 until worldTex.width) {
val c = worldTex.getPixel(x, y)
world[y * worldTex.width + x] = when (c) {
0x46712dff -> GRASS
0x9b9a9bff.toInt() -> STONE
0x6a5130ff -> DIRT
else -> AIR
}
}}
worldTex.dispose()
}
private val messageComposeBuffer = ByteArrayOutputStream(BLOCK_SIZE) // always use this and don't alter blockSendBuffer please
private var blockSendBuffer = ByteArray(1)
private var blockSendCount = 0
private fun resetBuf() {
blockSendCount = 0
messageComposeBuffer.reset()
}
override fun hasNext(): Boolean {
return (blockSendCount * BLOCK_SIZE < blockSendBuffer.size)
}
override fun startSendImpl(recipient: BlockTransferInterface): Int {
if (blockSendCount == 0) {
blockSendBuffer = messageComposeBuffer.toByteArray()
}
val sendSize = if (blockSendBuffer.size - (blockSendCount * BLOCK_SIZE) < BLOCK_SIZE)
blockSendBuffer.size % BLOCK_SIZE
else BLOCK_SIZE
recipient.writeout(ByteArray(sendSize) {
blockSendBuffer[blockSendCount * BLOCK_SIZE + it]
})
blockSendCount += 1
return sendSize
}
private var oldCmdbuf = HashMap<Int,Byte>(1024)
override fun writeoutImpl(inputData: ByteArray) {
val inputString = inputData.trimNull().toString(VM.CHARSET)
// prepare draw commands
/*
* draw command format:
*
* <Y> <X> <COL>
*
* marking rules:
*
* : exposed = has at least 1 nonsolid on 4 sides
*
* 1. exposed grass -> 2
* 2. exposed dirt -> 4
* 3. exposed stone -> 7
* 4. stone exposed to dirt/grass -> 7
*/
if (inputString.startsWith("POLL")) {
resetBuf()
val cmdbuf = HashMap<Int,Byte>(1024)
for (y in 1..H-2) { for (x in 1..W-2) {
val yx = (y-1).shl(8) or x
val i = y * W + x
val nearby = listOf(i-W,i-1,i+1,i+W).map { world[it] } // up, left, right, down
val block = world[i]
if (block == GRASS && nearby.contains(AIR)) {
cmdbuf[yx] = GRASS_OUT
}
else if (block == DIRT && nearby.contains(AIR)) {
cmdbuf[yx] = DIRT_OUT
}
else if (block == STONE && (nearby.contains(AIR) || nearby.contains(GRASS) || nearby.contains(DIRT))) {
cmdbuf[yx] = STONE_OUT
}
}}
(oldCmdbuf.keys union cmdbuf.keys).sorted().forEach { key ->
val value = (cmdbuf[key] ?: AIR_OUT).toInt()
val x = key % 256
val y = key / 256
messageComposeBuffer.write(y)
messageComposeBuffer.write(x)
messageComposeBuffer.write(value)
}
oldCmdbuf = cmdbuf
}
}
}

View File

@@ -0,0 +1,54 @@
package net.torvald.tsvm;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.backends.lwjgl3.Lwjgl3Application;
import com.badlogic.gdx.backends.lwjgl3.Lwjgl3ApplicationConfiguration;
import com.badlogic.gdx.graphics.glutils.ShaderProgram;
import kotlin.Pair;
import kotlin.collections.CollectionsKt;
import net.torvald.tsvm.peripheral.*;
public class AppLoader {
public static String appTitle = "tsvm";
public static Lwjgl3ApplicationConfiguration appConfig;
public static int WIDTH = 640;//810;//720;
public static int HEIGHT = 480;//360;//480;
public static void main(String[] args) {
ShaderProgram.pedantic = false;
appConfig = new Lwjgl3ApplicationConfiguration();
appConfig.setIdleFPS(60);
appConfig.setForegroundFPS(60);
appConfig.useVsync(false);
appConfig.setResizable(false);
appConfig.setTitle(appTitle);
appConfig.setWindowedMode(WIDTH, HEIGHT);
// VM vm = new VM(64 << 10, new TheRealWorld(), new VMProgramRom[]{BasicBios.INSTANCE, BasicRom.INSTANCE});
// VM vm = new VM(64 << 10, new TheRealWorld(), new VMProgramRom[]{OEMBios.INSTANCE, BasicRom.INSTANCE});
// VM vm = new VM(64 << 10, new TheRealWorld(), new VMProgramRom[]{TandemBios.INSTANCE, BasicRom.INSTANCE});
// VM vm = new VM(128 << 10, new TheRealWorld(), new VMProgramRom[]{BasicBios.INSTANCE, WPBios.INSTANCE});
VM vm = new VM(2048 << 10, new TheRealWorld(), new VMProgramRom[]{TsvmBios.INSTANCE});
VM pipvm = new VM(4096, new TheRealWorld(), new VMProgramRom[]{PipBios.INSTANCE, PipROM.INSTANCE});
EmulInstance reference = new EmulInstance(vm, "net.torvald.tsvm.peripheral.ReferenceGraphicsAdapter", "assets/disk0", 560, 448);
EmulInstance reference2 = new EmulInstance(vm, "net.torvald.tsvm.peripheral.ReferenceLikeLCD", "assets/disk0", 560, 448);
EmulInstance term = new EmulInstance(vm, "net.torvald.tsvm.peripheral.Term", "assets/disk0", 720, 480);
EmulInstance portable = new EmulInstance(vm, "net.torvald.tsvm.peripheral.CharacterLCDdisplay", "assets/disk0", 628, 302);
EmulInstance wp = new EmulInstance(vm, "net.torvald.tsvm.peripheral.WpTerm", "assets/wpdisk", 810, 360);
EmulInstance pip = new EmulInstance(pipvm, null, "assets/disk0", 640, 480, CollectionsKt.listOf(new Pair(1, new PeripheralEntry2(
32768L,
1,
0,
"net.torvald.tsvm.peripheral.ExtDisp",
pipvm, 160, 140
))));
new Lwjgl3Application(new VMGUI(pip, WIDTH, HEIGHT), appConfig);
}
}

View File

@@ -0,0 +1,127 @@
package net.torvald.tsvm
import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.toUint
import net.torvald.tsvm.peripheral.IOSpace
import java.awt.BorderLayout
import java.awt.Dimension
import java.awt.Font
import javax.swing.JFrame
import javax.swing.JTextArea
import javax.swing.WindowConstants
class Memvwr(val vm: VM) : JFrame() {
val memArea = JTextArea()
var columns = 16
fun composeVwrText() {
val sb = StringBuilder()
val io = vm.peripheralTable[0].peripheral as IOSpace
sb.append("== MMIO ==\n")
sb.append("Keyboard buffer: ")
for (i in 0L..31L) {
sb.append(io.peek(i)!!.toUByte().toString(16).padStart(2, '0').toUpperCase())
sb.append(' ')
}
sb.append('\n')
sb.append("Keyboard/Mouse input latched: ")
sb.append(io.peek(39L) != 0.toByte())
sb.append('\n')
sb.append("Mouse pos: ")
sb.append((io.peek(32L)!!.toUint() or (io.peek(33L)!!.toUint() shl 8)).toShort())
sb.append(", ")
sb.append((io.peek(34L)!!.toUint() or (io.peek(35L)!!.toUint() shl 8)).toShort())
sb.append(" (mouse down: ")
sb.append(io.peek(36L) != 0.toByte())
sb.append(")\n")
sb.append("Keys pressed: ")
for (i in 40L..47L) {
sb.append(io.peek(i)!!.toUByte().toString(16).padStart(2, '0').toUpperCase())
sb.append(' ')
}
sb.append('\n')
sb.append("TTY Keyboard read: ")
sb.append(io.peek(38L) != 0.toByte())
sb.append('\n')
sb.append("Counter latched: ")
sb.append(io.peek(68L)!!.toString(2).padStart(8, '0'))
sb.append('\n')
sb.append("\nBlock transfer status:\n")
for (port in 0..3) {
val status = io.peek(4084L + 2 * port)!!.toUint() or (io.peek(4085L + 2 * port)!!.toUint() shl 8)
sb.append("== Port ${port + 1}\n")
sb.append(" hasNext: ${(status and 0x8000) != 0}\n")
sb.append(" size of the block: ${if (status and 0xFFF == 0) 4096 else status and 0xFFF}\n")
}
sb.append("\nBlock transfer control:\n")
for (port in 0..3) {
val status = io.peek(4092L + port)!!
sb.append("== Port ${port + 1}: ${status.toString(2).padStart(8, '0')}\n")
}
sb.append("\n== First 4 kbytes of User RAM ==\n")
sb.append("ADRESS : 0 1 2 3| 4 5 6 7| 8 9 A B| C D E F\n")
for (i in 0L..4095L) {
if (i % columns == 0L) {
sb.append(i.toString(16).toUpperCase().padStart(6, '0')) // mem addr
sb.append(" : ") // separator
}
sb.append(vm.peek(i)!!.toUint().toString(16).toUpperCase().padStart(2, '0'))
if (i % 16L in longArrayOf(3L, 7L, 11L)) {
sb.append('|') // mem value
}
else {
sb.append(' ') // mem value
}
// ASCII viewer
if (i % columns == 15L) {
sb.append("| ")
for (x in -15..0) {
val mem = vm.peek(i + x)!!.toUint()
if (mem < 32) {
sb.append('.')
}
else {
sb.append(mem.toChar())
}
if (x + 15 in intArrayOf(3, 7, 11))
sb.append(' ')
}
sb.append("|\n")
}
}
memArea.text = sb.toString()
}
fun update() {
composeVwrText()
}
init {
memArea.font = Font("Monospaced", Font.PLAIN, 12)
memArea.highlighter = null
this.layout = BorderLayout()
this.isVisible = true
this.add(javax.swing.JScrollPane(memArea), BorderLayout.CENTER)
this.defaultCloseOperation = WindowConstants.EXIT_ON_CLOSE
this.size = Dimension(820, 960)
}
}

View File

@@ -0,0 +1,280 @@
package net.torvald.tsvm
import com.badlogic.gdx.ApplicationAdapter
import com.badlogic.gdx.Gdx
import com.badlogic.gdx.graphics.*
import com.badlogic.gdx.graphics.g2d.SpriteBatch
import kotlinx.coroutines.*
import net.torvald.terrarum.modulecomputers.tsvmperipheral.WorldRadar
import net.torvald.tsvm.peripheral.*
import java.io.File
class EmulInstance(
val vm: VM,
val display: String?,
val diskPath: String = "assets/disk0",
val drawWidth: Int,
val drawHeight: Int,
) {
var extraPeripherals: List<Pair<Int, PeripheralEntry2>> = listOf(); private set
constructor(
vm: VM,
display: String?,
diskPath: String = "assets/disk0",
drawWidth: Int,
drawHeight: Int,
extraPeripherals: List<Pair<Int, PeripheralEntry2>>
) : this(vm, display, diskPath, drawWidth, drawHeight) {
this.extraPeripherals = extraPeripherals
}
}
class VMGUI(val loaderInfo: EmulInstance, val viewportWidth: Int, val viewportHeight: Int) : ApplicationAdapter() {
val vm = loaderInfo.vm
lateinit var batch: SpriteBatch
lateinit var camera: OrthographicCamera
var gpu: GraphicsAdapter? = null
lateinit var vmRunner: VMRunner
lateinit var coroutineJob: Job
lateinit var memvwr: Memvwr
lateinit var fullscreenQuad: Mesh
val usememvwr = false
override fun create() {
super.create()
fullscreenQuad = Mesh(
true, 4, 6,
VertexAttribute.Position(),
VertexAttribute.ColorUnpacked(),
VertexAttribute.TexCoords(0)
)
updateFullscreenQuad(AppLoader.WIDTH, AppLoader.HEIGHT)
batch = SpriteBatch()
camera = OrthographicCamera(AppLoader.WIDTH.toFloat(), AppLoader.HEIGHT.toFloat())
camera.setToOrtho(false)
camera.update()
batch.projectionMatrix = camera.combined
init()
}
private fun init() {
if (loaderInfo.display != null) {
val loadedClass = Class.forName(loaderInfo.display)
val loadedClassConstructor = loadedClass.getConstructor(vm::class.java)
val loadedClassInstance = loadedClassConstructor.newInstance(vm)
gpu = (loadedClassInstance as GraphicsAdapter)
vm.getIO().blockTransferPorts[0].attachDevice(TestDiskDrive(vm, 0, File(loaderInfo.diskPath)))
vm.peripheralTable[1] = PeripheralEntry(
gpu,
GraphicsAdapter.VRAM_SIZE,
16,
0
)
vm.getPrintStream = { gpu!!.getPrintStream() }
vm.getErrorStream = { gpu!!.getErrorStream() }
vm.getInputStream = { gpu!!.getInputStream() }
}
else {
vm.getPrintStream = { System.out }
vm.getErrorStream = { System.err }
vm.getInputStream = { System.`in` }
}
vm.getIO().blockTransferPorts[1].attachDevice(
WorldRadar(
Gdx.files.internal(
"test_assets/test_terrain.png"
)
)
)
loaderInfo.extraPeripherals.forEach { (port, peri) ->
val typeargs = peri.args.map { it.javaClass }.toTypedArray()
val loadedClass = Class.forName(peri.peripheralClassname)
val loadedClassConstructor = loadedClass.getConstructor(*typeargs)
val loadedClassInstance = loadedClassConstructor.newInstance(*peri.args)
vm.peripheralTable[port] = PeripheralEntry(
loadedClassInstance as PeriBase,
peri.memsize,
peri.mmioSize,
peri.interruptCount
)
}
Gdx.input.inputProcessor = vm.getIO()
if (usememvwr) memvwr = Memvwr(vm)
vmRunner = VMRunnerFactory(vm, "js")
coroutineJob = GlobalScope.launch {
vmRunner.executeCommand(vm.roms[0]!!.readAll())
}
}
private var rebootRequested = false
private fun reboot() {
vmRunner.close()
coroutineJob.cancel("reboot requested")
vm.init()
init()
}
private var updateAkku = 0.0
private var updateRate = 1f / 60f
override fun render() {
gdxClearAndSetBlend(.094f, .094f, .094f, 0f)
setCameraPosition(0f, 0f)
// update window title with contents of the 'built-in status display'
val msg = (1024L until 1048L).map { cp437toUni[vm.getIO().mmio_read(it)!!.toInt().and(255)] }.joinToString("").trim()
Gdx.graphics.setTitle("$msg $EMDASH F: ${Gdx.graphics.framesPerSecond}")
if (usememvwr) memvwr.update()
super.render()
val dt = Gdx.graphics.rawDeltaTime
updateAkku += dt
var i = 0L
while (updateAkku >= updateRate) {
updateGame(updateRate)
updateAkku -= updateRate
i += 1
}
renderGame(dt)
}
private fun updateGame(delta: Float) {
if (!vm.resetDown && rebootRequested) {
reboot()
rebootRequested = false
}
vm.update(delta)
if (vm.resetDown) rebootRequested = true
}
fun poke(addr: Long, value: Byte) = vm.poke(addr, value)
private val defaultGuiBackgroundColour = Color(0x444444ff)
private fun renderGame(delta: Float) {
val clearCol = gpu?.getBackgroundColour() ?: defaultGuiBackgroundColour
Gdx.gl.glClearColor(clearCol.r, clearCol.g, clearCol.b, clearCol.a)
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT)
gpu?.render(delta, batch, (viewportWidth - loaderInfo.drawWidth).div(2).toFloat(), (viewportHeight - loaderInfo.drawHeight).div(2).toFloat())
vm.findPeribyType("oled")?.let {
val disp = it.peripheral as ExtDisp
disp.render(batch,
(viewportWidth - loaderInfo.drawWidth).div(2).toFloat() + (gpu?.config?.width ?: 0),
(viewportHeight - loaderInfo.drawHeight).div(2).toFloat())
}
}
private fun setCameraPosition(newX: Float, newY: Float) {
camera.position.set((-newX + AppLoader.WIDTH / 2), (-newY + AppLoader.HEIGHT / 2), 0f) // deliberate integer division
camera.update()
batch.setProjectionMatrix(camera.combined)
}
private fun gdxClearAndSetBlend(r: Float, g: Float, b: Float, a: Float) {
Gdx.gl.glClearColor(r,g,b,a)
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT)
Gdx.gl.glEnable(GL20.GL_TEXTURE_2D)
Gdx.gl.glEnable(GL20.GL_BLEND)
}
private fun updateFullscreenQuad(WIDTH: Int, HEIGHT: Int) { // NOT y-flipped quads!
fullscreenQuad.setVertices(floatArrayOf(
0f, 0f, 0f, 1f, 1f, 1f, 1f, 0f, 1f,
WIDTH.toFloat(), 0f, 0f, 1f, 1f, 1f, 1f, 1f, 1f,
WIDTH.toFloat(), HEIGHT.toFloat(), 0f, 1f, 1f, 1f, 1f, 1f, 0f,
0f, HEIGHT.toFloat(), 0f, 1f, 1f, 1f, 1f, 0f, 0f
))
fullscreenQuad.setIndices(shortArrayOf(0, 1, 2, 2, 3, 0))
}
override fun dispose() {
super.dispose()
batch.dispose()
fullscreenQuad.dispose()
coroutineJob.cancel()
vm.dispose()
}
companion object {
val cp437toUni = hashMapOf<Int, Char>(
0 to 32.toChar(),
1 to 0x263A.toChar(),
2 to 0x263B.toChar(),
3 to 0x2665.toChar(),
4 to 0x2666.toChar(),
5 to 0x2663.toChar(),
6 to 0x2660.toChar(),
7 to 0x2022.toChar(),
8 to 0x25D8.toChar(),
9 to 0x25CB.toChar(),
10 to 0x25D9.toChar(),
11 to 0x2642.toChar(),
12 to 0x2640.toChar(),
13 to 0x266A.toChar(),
14 to 0x266B.toChar(),
15 to 0x00A4.toChar(),
16 to 0x25BA.toChar(),
17 to 0x25C4.toChar(),
18 to 0x2195.toChar(),
19 to 0x203C.toChar(),
20 to 0x00B6.toChar(),
21 to 0x00A7.toChar(),
22 to 0x25AC.toChar(),
23 to 0x21A8.toChar(),
24 to 0x2191.toChar(),
25 to 0x2193.toChar(),
26 to 0x2192.toChar(),
27 to 0x2190.toChar(),
28 to 0x221F.toChar(),
29 to 0x2194.toChar(),
30 to 0x25B2.toChar(),
31 to 0x25BC.toChar(),
127 to 0x2302.toChar(),
158 to 0x2610.toChar(),
159 to 0x2611.toChar()
)
init {
for (k in 32..126) {
cp437toUni[k] = k.toChar()
}
}
}
}
const val EMDASH = 0x2014.toChar()