mirror of
https://github.com/curioustorvald/tsvm.git
synced 2026-03-07 11:51:49 +09:00
codes split into modules: tsvm_core, tsvm_executable, TerranBASICexecutable
This commit is contained in:
3
TerranBASICexecutable/META-INF/MANIFEST.MF
Normal file
3
TerranBASICexecutable/META-INF/MANIFEST.MF
Normal file
@@ -0,0 +1,3 @@
|
||||
Manifest-Version: 1.0
|
||||
Main-Class: net.torvald.tsvm.TerranBASIC
|
||||
|
||||
23
TerranBASICexecutable/TerranBASICexecutable.iml
Normal file
23
TerranBASICexecutable/TerranBASICexecutable.iml
Normal file
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="module" module-name="tsvm_core" />
|
||||
<orderEntry type="library" name="com.badlogicgames.gdx:gdx-backend-lwjgl3:1.10.0" level="project" />
|
||||
<orderEntry type="library" name="com.badlogicgames.gdx:gdx-platform:1.10.0" level="project" />
|
||||
<orderEntry type="library" name="com.badlogicgames.gdx:gdx:1.10.0" level="project" />
|
||||
<orderEntry type="library" name="gdx-platform-1.10.0-natives-desktop" level="project" />
|
||||
<orderEntry type="library" name="GetCpuName" level="project" />
|
||||
<orderEntry type="library" name="js-21.1.0-edit" level="project" />
|
||||
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
|
||||
<orderEntry type="library" name="org.graalvm.js:js-scriptengine:21.1.0" level="project" />
|
||||
<orderEntry type="library" name="org.graalvm.js:js:21.1.0" level="project" />
|
||||
<orderEntry type="library" name="org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.1" level="project" />
|
||||
<orderEntry type="library" name="TerranVirtualDisk" level="project" />
|
||||
</component>
|
||||
</module>
|
||||
32
TerranBASICexecutable/src/net/torvald/tsvm/TerranBASIC.java
Normal file
32
TerranBASICexecutable/src/net/torvald/tsvm/TerranBASIC.java
Normal file
@@ -0,0 +1,32 @@
|
||||
package net.torvald.tsvm;
|
||||
|
||||
import com.badlogic.gdx.backends.lwjgl3.Lwjgl3Application;
|
||||
import com.badlogic.gdx.backends.lwjgl3.Lwjgl3ApplicationConfiguration;
|
||||
import com.badlogic.gdx.graphics.glutils.ShaderProgram;
|
||||
import net.torvald.tsvm.peripheral.*;
|
||||
|
||||
public class TerranBASIC {
|
||||
|
||||
public static String appTitle = "TerranBASIC";
|
||||
public static Lwjgl3ApplicationConfiguration appConfig;
|
||||
|
||||
public static int WIDTH = 640;
|
||||
public static int HEIGHT = 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 tbasvm = new VM(64 << 10, new TheRealWorld(), new VMProgramRom[]{TBASRelBios.INSTANCE});
|
||||
EmulInstance tbasrunner = new EmulInstance(tbasvm, "net.torvald.tsvm.peripheral.ReferenceGraphicsAdapter", "assets/disk0", 560, 448);
|
||||
new Lwjgl3Application(new VMGUI(tbasrunner, WIDTH, HEIGHT), appConfig);
|
||||
}
|
||||
}
|
||||
264
TerranBASICexecutable/src/net/torvald/tsvm/VMGUI.kt
Normal file
264
TerranBASICexecutable/src/net/torvald/tsvm/VMGUI.kt
Normal file
@@ -0,0 +1,264 @@
|
||||
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.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 fullscreenQuad: Mesh
|
||||
|
||||
override fun create() {
|
||||
super.create()
|
||||
|
||||
fullscreenQuad = Mesh(
|
||||
true, 4, 6,
|
||||
VertexAttribute.Position(),
|
||||
VertexAttribute.ColorUnpacked(),
|
||||
VertexAttribute.TexCoords(0)
|
||||
)
|
||||
updateFullscreenQuad(TerranBASIC.WIDTH, TerranBASIC.HEIGHT)
|
||||
|
||||
batch = SpriteBatch()
|
||||
camera = OrthographicCamera(TerranBASIC.WIDTH.toFloat(), TerranBASIC.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` }
|
||||
}
|
||||
|
||||
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()
|
||||
|
||||
|
||||
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}")
|
||||
|
||||
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 + TerranBASIC.WIDTH / 2), (-newY + TerranBASIC.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()
|
||||
Reference in New Issue
Block a user