improved way of initialising vms at (re)launch

This commit is contained in:
minjaesong
2023-01-04 19:09:42 +09:00
parent f27caded9b
commit ceddf2c5b9
20 changed files with 275 additions and 177 deletions

View File

@@ -38,8 +38,8 @@ if (statusCode != 0) {
let readCount = 0 let readCount = 0
function readBytes(length) { function readBytes(length, ptrToDecode) {
let ptr = sys.malloc(length) let ptr = (ptrToDecode === undefined) ? sys.malloc(length) : ptrToDecode
let requiredBlocks = Math.floor((readCount + length) / 4096) - Math.floor(readCount / 4096) let requiredBlocks = Math.floor((readCount + length) / 4096) - Math.floor(readCount / 4096)
let completedReads = 0 let completedReads = 0
@@ -108,6 +108,8 @@ audio.setMasterVolume(0, 255)
// FIXME: when a playback was interrupted using SHIFT-CTRL-T-R, then re-tried, the ghost from the previous run // FIXME: when a playback was interrupted using SHIFT-CTRL-T-R, then re-tried, the ghost from the previous run
// briefly manifests, even if you're queueing only once // briefly manifests, even if you're queueing only once
const decodePtr = sys.malloc(BLOCK_SIZE)
while (sampleSize > 0) { while (sampleSize > 0) {
let queueSize = audio.getPosition(0) let queueSize = audio.getPosition(0)
@@ -123,14 +125,13 @@ while (sampleSize > 0) {
// upload four samples for lag-safely // upload four samples for lag-safely
for (let repeat = QUEUE_MAX - queueSize; repeat > 0; repeat--) { for (let repeat = QUEUE_MAX - queueSize; repeat > 0; repeat--) {
let readLength = (sampleSize < BLOCK_SIZE) ? sampleSize : BLOCK_SIZE let readLength = (sampleSize < BLOCK_SIZE) ? sampleSize : BLOCK_SIZE
let samples = readBytes(readLength) let samples = readBytes(readLength, decodePtr)
audio.putPcmDataByPtr(samples, readLength, 0) audio.putPcmDataByPtr(samples, readLength, 0)
audio.setSampleUploadLength(0, readLength) audio.setSampleUploadLength(0, readLength)
audio.startSampleUpload(0) audio.startSampleUpload(0)
sampleSize -= readLength sampleSize -= readLength
sys.free(samples)
if (repeat > 1) sys.sleep(10) if (repeat > 1) sys.sleep(10)
} }
@@ -145,3 +146,4 @@ while (sampleSize > 0) {
} }
audio.stop(0) // this shouldn't be necessary, it should stop automatically audio.stop(0) // this shouldn't be necessary, it should stop automatically
sys.free(samples)

View File

@@ -1094,7 +1094,7 @@ var execApp = (cmdsrc, args, appname) => {
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
// Boot script // Boot script
serial.println("TVDOS.SYS initialised, running boot script..."); serial.println(`TVDOS.SYS initialised on VM ${sys.getVmId()}, running boot script...`);
var _G = {}; var _G = {};
let cmdfile = files.open("A:/tvdos/bin/command.js") let cmdfile = files.open("A:/tvdos/bin/command.js")
eval(`var _AUTOEXEC=function(exec_args){${cmdfile.sread()}\n};` + eval(`var _AUTOEXEC=function(exec_args){${cmdfile.sread()}\n};` +

View File

@@ -74,36 +74,6 @@ for (let f = 1; ; f++) {
// insert sync packet // insert sync packet
if (f > 1) appendToOutfile(syncPacket) if (f > 1) appendToOutfile(syncPacket)
// insert video frame
if (f <= TOTAL_FRAMES) {
let fname = PATHFUN(f)
let framefile = files.open(_G.shell.resolvePathInput(fname).full)
let fileLen = framefile.size
framefile.pread(infile, fileLen)
let [_1, _2, channels, _3] = graphics.decodeImageTo(infile, fileLen, imagearea)
print(`Frame ${f}/${TOTAL_FRAMES} (Ch: ${channels}) ->`)
// graphics.imageToDisplayableFormat(imagearea, decodearea, 560, 448, 3, 1)
ipfFun(imagearea, ipfarea, WIDTH, HEIGHT, channels, false, f)
let gzlen = gzip.compFromTo(ipfarea, FBUF_SIZE, gzippedImage)
let frameSize = [
(gzlen >>> 0) & 255,
(gzlen >>> 8) & 255,
(gzlen >>> 16) & 255,
(gzlen >>> 24) & 255
]
appendToOutfile(packetType)
appendToOutfile(frameSize)
appendToOutfilePtr(gzippedImage, gzlen)
print(` ${gzlen} bytes\n`)
}
// insert audio track, if any // insert audio track, if any
if (audioRemaining > 0) { if (audioRemaining > 0) {
@@ -136,6 +106,36 @@ for (let f = 1; ; f++) {
audioRemaining -= actualBytesToRead audioRemaining -= actualBytesToRead
} }
} }
// insert video frame
if (f <= TOTAL_FRAMES) {
let fname = PATHFUN(f)
let framefile = files.open(_G.shell.resolvePathInput(fname).full)
let fileLen = framefile.size
framefile.pread(infile, fileLen)
let [_1, _2, channels, _3] = graphics.decodeImageTo(infile, fileLen, imagearea)
print(`Frame ${f}/${TOTAL_FRAMES} (Ch: ${channels}) ->`)
// graphics.imageToDisplayableFormat(imagearea, decodearea, 560, 448, 3, 1)
ipfFun(imagearea, ipfarea, WIDTH, HEIGHT, channels, false, f)
let gzlen = gzip.compFromTo(ipfarea, FBUF_SIZE, gzippedImage)
let frameSize = [
(gzlen >>> 0) & 255,
(gzlen >>> 8) & 255,
(gzlen >>> 16) & 255,
(gzlen >>> 24) & 255
]
appendToOutfile(packetType)
appendToOutfile(frameSize)
appendToOutfilePtr(gzippedImage, gzlen)
print(` ${gzlen} bytes\n`)
}
// if there is no video and audio remaining, exit the loop // if there is no video and audio remaining, exit the loop
if (f > TOTAL_FRAMES && audioRemaining <= 0) break if (f > TOTAL_FRAMES && audioRemaining <= 0) break

View File

@@ -29,8 +29,8 @@ con.clear(); con.curs_set(0)
let readCount = 0 let readCount = 0
function readBytes(length) { function readBytes(length, ptrToDecode) {
let ptr = sys.malloc(length) let ptr = (ptrToDecode === undefined) ? sys.malloc(length) : ptrToDecode
let requiredBlocks = Math.floor((readCount + length) / 4096) - Math.floor(readCount / 4096) let requiredBlocks = Math.floor((readCount + length) / 4096) - Math.floor(readCount / 4096)
let completedReads = 0 let completedReads = 0
@@ -156,8 +156,8 @@ let ipfbuf = sys.malloc(FBUF_SIZE)
graphics.setGraphicsMode(4) graphics.setGraphicsMode(4)
let startTime = sys.nanoTime() let startTime = sys.nanoTime()
let framesRead = 0 let framesRead = 0
let audioFired = false
audio.resetParams(0) audio.resetParams(0)
audio.purgeQueue(0) audio.purgeQueue(0)
@@ -182,6 +182,8 @@ while (readCount < FILE_LENGTH) {
let packetType = readShort() let packetType = readShort()
// ideally, first two packets will be audio packets
// sync packets // sync packets
if (65535 == packetType) { if (65535 == packetType) {
frameUnit -= 1 frameUnit -= 1
@@ -204,6 +206,12 @@ while (readCount < FILE_LENGTH) {
if (frameUnit == 1) { if (frameUnit == 1) {
gzip.decompFromTo(gzippedPtr, payloadLen, ipfbuf) // should return FBUF_SIZE gzip.decompFromTo(gzippedPtr, payloadLen, ipfbuf) // should return FBUF_SIZE
decodefun(ipfbuf, -1048577, -1310721, width, height, (packetType & 255) == 5) decodefun(ipfbuf, -1048577, -1310721, width, height, (packetType & 255) == 5)
// defer audio playback until a first frame is sent
if (!audioFired) {
audio.play(0)
audioFired = true
}
} }
sys.free(gzippedPtr) sys.free(gzippedPtr)
@@ -221,7 +229,6 @@ while (readCount < FILE_LENGTH) {
audio.putPcmDataByPtr(samples, readLength, 0) audio.putPcmDataByPtr(samples, readLength, 0)
audio.setSampleUploadLength(0, readLength) audio.setSampleUploadLength(0, readLength)
audio.startSampleUpload(0) audio.startSampleUpload(0)
audio.play(0)
sys.free(samples) sys.free(samples)
} }

View File

@@ -7,7 +7,11 @@ import net.torvald.tsvm.peripheral.AudioAdapter
*/ */
class AudioJSR223Delegate(private val vm: VM) { class AudioJSR223Delegate(private val vm: VM) {
private fun getFirstSnd(): AudioAdapter? = vm.findPeribyType(VM.PERITYPE_SOUND)?.peripheral as? AudioAdapter private fun getFirstSnd(): AudioAdapter? {
val a = vm.findPeribyType(VM.PERITYPE_SOUND)?.peripheral as? AudioAdapter
println("get AudioAdapter: $a; vm: $vm")
return a
}
private fun getPlayhead(playhead: Int) = getFirstSnd()?.playheads?.get(playhead) private fun getPlayhead(playhead: Int) = getFirstSnd()?.playheads?.get(playhead)
fun setPcmMode(playhead: Int) { getPlayhead(playhead)?.isPcmMode = true } fun setPcmMode(playhead: Int) { getPlayhead(playhead)?.isPcmMode = true }
@@ -56,9 +60,9 @@ class AudioJSR223Delegate(private val vm: VM) {
} }
fun getPcmData(index: Int) = getFirstSnd()?.pcmBin?.get(index.toLong()) fun getPcmData(index: Int) = getFirstSnd()?.pcmBin?.get(index.toLong())
fun setPcmQueueSizeIndex(playhead: Int, index: Int) { getPlayhead(playhead)?.pcmQueueSizeIndex = index } fun setPcmQueueCapacityIndex(playhead: Int, index: Int) { getPlayhead(playhead)?.pcmQueueSizeIndex = index }
fun getPcmQueueSizeIndex(playhead: Int, index: Int) { getPlayhead(playhead)?.pcmQueueSizeIndex } fun getPcmQueueCapacityIndex(playhead: Int, index: Int) { getPlayhead(playhead)?.pcmQueueSizeIndex }
fun getPcmQueueSize(playhead: Int, index: Int) { getPlayhead(playhead)?.getPcmQueueSize() } fun getPcmQueueCapacity(playhead: Int, index: Int) { getPlayhead(playhead)?.getPcmQueueCapacity() }
fun resetParams(playhead: Int) { fun resetParams(playhead: Int) {
getPlayhead(playhead)?.resetParams() getPlayhead(playhead)?.resetParams()

View File

@@ -465,7 +465,7 @@ class GraphicsJSR223Delegate(private val vm: VM) {
} }
} }
else if (1 == useDither) { else if (1 == useDither) {
val srcimg = UnsafeHelper.allocate(width * height * 4L * channels) // array of floats! val srcimg = UnsafeHelper.allocate(width * height * 4L * channels, this) // array of floats!
for (k in 0L until len) { for (k in 0L until len) {
srcimg.setFloat(channels * k + 0, vm.peek(srcPtr + channels * k + 0)!!.toUint().toFloat() / 255f) srcimg.setFloat(channels * k + 0, vm.peek(srcPtr + channels * k + 0)!!.toUint().toFloat() / 255f)

View File

@@ -24,9 +24,9 @@ internal object UnsafeHelper {
/** /**
* A factory method to allocate a memory of given size and return its starting address as a pointer. * A factory method to allocate a memory of given size and return its starting address as a pointer.
*/ */
fun allocate(size: Long): UnsafePtr { fun allocate(size: Long, caller: Any): UnsafePtr {
val ptr = unsafe.allocateMemory(size) val ptr = unsafe.allocateMemory(size)
return UnsafePtr(ptr, size) return UnsafePtr(ptr, size, caller)
} }
fun memcpy(src: UnsafePtr, fromIndex: Long, dest: UnsafePtr, toIndex: Long, copyLength: Long) = fun memcpy(src: UnsafePtr, fromIndex: Long, dest: UnsafePtr, toIndex: Long, copyLength: Long) =
@@ -58,7 +58,7 @@ internal object UnsafeHelper {
* *
* Use of hashCode() is forbidden, use the pointer instead. * Use of hashCode() is forbidden, use the pointer instead.
*/ */
internal class UnsafePtr(pointer: Long, allocSize: Long) { internal class UnsafePtr(pointer: Long, allocSize: Long, private val caller: Any) {
var destroyed = false var destroyed = false
private set private set
@@ -162,7 +162,7 @@ internal class UnsafePtr(pointer: Long, allocSize: Long) {
UnsafeHelper.unsafe.setMemory(ptr, size, byte) UnsafeHelper.unsafe.setMemory(ptr, size, byte)
} }
override fun toString() = "0x${ptr.toString(16)} with size $size" override fun toString() = "0x${ptr.toString(16)} with size $size, created by $caller"
override fun equals(other: Any?) = this.ptr == (other as UnsafePtr).ptr && this.size == other.size override fun equals(other: Any?) = this.ptr == (other as UnsafePtr).ptr && this.size == other.size
inline fun printStackTrace(obj: Any) = printStackTrace(obj, System.out) // because of Java inline fun printStackTrace(obj: Any) = printStackTrace(obj, System.out) // because of Java

View File

@@ -0,0 +1,6 @@
package net.torvald.tsvm
/**
* Created by minjaesong on 2023-01-04.
*/
fun getHashStr(length: Int = 5) = (0 until length).map { "YBNDRFG8EJKMCPQXOTLVWIS2A345H769"[Math.random().times(32).toInt()] }.joinToString("")

View File

@@ -14,6 +14,10 @@ import kotlin.math.ceil
class ErrorIllegalAccess(vm: VM, addr: Long) : RuntimeException("Segmentation fault at 0x${addr.toString(16).padStart(8, '0')} on VM id ${vm.id}") class ErrorIllegalAccess(vm: VM, addr: Long) : RuntimeException("Segmentation fault at 0x${addr.toString(16).padStart(8, '0')} on VM id ${vm.id}")
inline class VmId(val text: String) {
override fun toString() = text
}
/** /**
* A class representing an instance of a Virtual Machine * A class representing an instance of a Virtual Machine
@@ -30,7 +34,9 @@ class VM(
val peripheralSlots = _peripheralSlots.coerceIn(1,8) val peripheralSlots = _peripheralSlots.coerceIn(1,8)
val id = java.util.Random().nextInt() val id = VmId(getHashStr(6).let { it.substring(0..2) + "-" + it.substring(3..5) })
override fun toString() = "tsvm.VM!$id"
internal val contexts = ArrayList<Thread>() internal val contexts = ArrayList<Thread>()
@@ -38,7 +44,7 @@ class VM(
val MALLOC_UNIT = 64 val MALLOC_UNIT = 64
private val mallocBlockSize = (memsize / MALLOC_UNIT).toInt() private val mallocBlockSize = (memsize / MALLOC_UNIT).toInt()
internal val usermem = UnsafeHelper.allocate(memsize) internal val usermem = UnsafeHelper.allocate(memsize, this)
val peripheralTable = Array(peripheralSlots) { PeripheralEntry() } val peripheralTable = Array(peripheralSlots) { PeripheralEntry() }

View File

@@ -13,6 +13,8 @@ import java.nio.charset.Charset
*/ */
class VMJSR223Delegate(private val vm: VM) { class VMJSR223Delegate(private val vm: VM) {
fun getVmId() = vm.id.toString()
fun poke(addr: Int, value: Int) = vm.poke(addr.toLong(), value.toByte()) fun poke(addr: Int, value: Int) = vm.poke(addr.toLong(), value.toByte())
fun peek(addr: Int) = vm.peek(addr.toLong())!!.toInt().and(255) fun peek(addr: Int) = vm.peek(addr.toLong())!!.toInt().and(255)
fun nanoTime() = System.nanoTime() fun nanoTime() = System.nanoTime()

View File

@@ -2,11 +2,15 @@ package net.torvald.tsvm
import com.badlogic.gdx.Gdx import com.badlogic.gdx.Gdx
import com.badlogic.gdx.utils.GdxRuntimeException import com.badlogic.gdx.utils.GdxRuntimeException
import com.badlogic.gdx.utils.JsonValue
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.cancel import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import net.torvald.tsvm.peripheral.BlockTransferInterface
import net.torvald.tsvm.peripheral.GraphicsAdapter import net.torvald.tsvm.peripheral.GraphicsAdapter
import net.torvald.tsvm.peripheral.PeriBase
import net.torvald.tsvm.peripheral.VMProgramRom
/** /**
* Created by minjaesong on 2022-12-15. * Created by minjaesong on 2022-12-15.
@@ -21,7 +25,7 @@ object VMSetupBroker {
* @param vmRunners Hashmap on the host of VMs that holds the instances of the VMRunners for the given VM. Key: Int(VM's identifier), value: [net.torvald.tsvm.VMRunner] * @param vmRunners Hashmap on the host of VMs that holds the instances of the VMRunners for the given VM. Key: Int(VM's identifier), value: [net.torvald.tsvm.VMRunner]
* @param coroutineJobs Hashmap on the host of VMs that holds the coroutine-job object for the currently running VM-instance. Key: Int(VM's identifier), value: [kotlinx.coroutines.Job] * @param coroutineJobs Hashmap on the host of VMs that holds the coroutine-job object for the currently running VM-instance. Key: Int(VM's identifier), value: [kotlinx.coroutines.Job]
*/ */
fun initVMenv(vm: VM, gpu: GraphicsAdapter, vmRunners: HashMap<Int, VMRunner>, coroutineJobs: HashMap<Int, Job>, whatToDoOnVmException: (Throwable) -> Unit) { fun initVMenv(vm: VM, profileJson: JsonValue, profileName: String, gpu: GraphicsAdapter, vmRunners: HashMap<VmId, VMRunner>, coroutineJobs: HashMap<VmId, Job>, whatToDoOnVmException: (Throwable) -> Unit) {
vm.init() vm.init()
try { try {
@@ -29,6 +33,8 @@ object VMSetupBroker {
} }
catch (_: GdxRuntimeException) {} // pixmap already disposed catch (_: GdxRuntimeException) {} // pixmap already disposed
installPeripherals(vm, profileJson, profileName)
vm.peripheralTable[1] = PeripheralEntry(gpu)//, GraphicsAdapter.VRAM_SIZE, 16, 0) vm.peripheralTable[1] = PeripheralEntry(gpu)//, GraphicsAdapter.VRAM_SIZE, 16, 0)
vm.getPrintStream = { gpu.getPrintStream() } vm.getPrintStream = { gpu.getPrintStream() }
@@ -54,8 +60,10 @@ object VMSetupBroker {
* @param vmRunners Hashmap on the host of VMs that holds the instances of the VMRunners for the given VM. Key: Int(VM's identifier), value: [net.torvald.tsvm.VMRunner] * @param vmRunners Hashmap on the host of VMs that holds the instances of the VMRunners for the given VM. Key: Int(VM's identifier), value: [net.torvald.tsvm.VMRunner]
* @param coroutineJobs Hashmap on the host of VMs that holds the coroutine-job object for the currently running VM-instance. Key: Int(VM's identifier), value: [kotlinx.coroutines.Job] * @param coroutineJobs Hashmap on the host of VMs that holds the coroutine-job object for the currently running VM-instance. Key: Int(VM's identifier), value: [kotlinx.coroutines.Job]
*/ */
fun killVMenv(vm: VM, vmRunners: HashMap<Int, VMRunner>, coroutineJobs: HashMap<Int, Job>) { fun killVMenv(vm: VM, vmRunners: HashMap<VmId, VMRunner>, coroutineJobs: HashMap<VmId, Job>) {
vm.park() vm.park()
vm.poke(-90L, -128)
for (i in 1 until vm.peripheralTable.size) { for (i in 1 until vm.peripheralTable.size) {
try { try {
@@ -64,13 +72,108 @@ object VMSetupBroker {
catch (_: Throwable) {} catch (_: Throwable) {}
} }
coroutineJobs[vm.id]?.cancel("VM kill command received")
vmRunners[vm.id]?.close()
vm.getPrintStream = { TODO() } vm.getPrintStream = { TODO() }
vm.getErrorStream = { TODO() } vm.getErrorStream = { TODO() }
vm.getInputStream = { TODO() } vm.getInputStream = { TODO() }
vm.poke(-90L, -128)
vmRunners[vm.id]?.close()
coroutineJobs[vm.id]?.cancel("VM kill command received")
} }
/**
* You'll want to further init the things using the VM this function returns, such as:
*
* ```
* makeVMfromJson(json.get(NAME)).let{
* initVMemv(it)
* vms[VIEWPORT_INDEX] = VMRunnerInfo(it, NAME)
* }
* ```
*/
private fun installPeripherals(vm: VM, json: JsonValue, profileName: String): VM {
println("Processing profile '$profileName'")
val cardslots = json.getInt("cardslots")
// install peripherals
listOf("com1", "com2", "com3", "com4").map { json.get(it) }.forEachIndexed { index, jsonValue ->
jsonValue?.let { deviceInfo ->
val className = deviceInfo.getString("cls")
val loadedClass = Class.forName(className)
val argTypess = loadedClass.declaredConstructors
var successful = false
var k = 0
// just try out all the possible argTypes
while (!successful && k < argTypess.size) {
try {
val argTypes = argTypess[k].parameterTypes
println("loadedClass = $className")
println("trying constructor args[${k}/${argTypess.lastIndex}]: ${argTypes.joinToString { it.canonicalName }}")
val args = deviceInfo.get("args").allIntoJavaType(argTypes.tail())
val loadedClassConstructor = loadedClass.getConstructor(*argTypes)
val loadedClassInstance = loadedClassConstructor.newInstance(vm, *args)
vm.getIO().blockTransferPorts[index].attachDevice(loadedClassInstance as BlockTransferInterface)
println("COM${index+1} = ${loadedClassInstance.javaClass.canonicalName}: ${args.joinToString()}")
successful = true
}
catch (e: IllegalArgumentException) {
// e.printStackTrace()
}
finally {
k += 1
}
}
if (!successful) {
throw RuntimeException("Invalid or insufficient arguments for $className in the profile $profileName")
}
}
}
(2..cardslots).map { it to json.get("card$it") }.forEach { (index, jsonValue) ->
jsonValue?.let { deviceInfo ->
val className = deviceInfo.getString("cls")
val loadedClass = Class.forName(className)
val argTypes = loadedClass.declaredConstructors[0].parameterTypes
val args = deviceInfo.get("args").allIntoJavaType(argTypes.tail())
val loadedClassConstructor = loadedClass.getConstructor(*argTypes)
val loadedClassInstance = loadedClassConstructor.newInstance(vm, *args)
val peri = loadedClassInstance as PeriBase
vm.peripheralTable[index] = PeripheralEntry(
peri
)
}
}
return vm
}
private fun JsonValue.allIntoJavaType(argTypes: Array<Class<*>>): Array<Any?> {
val values = this.iterator().toList()
if (values.size != argTypes.size) throw IllegalArgumentException("# of args: ${values.size}, # of arg types: ${argTypes.size}")
return argTypes.mapIndexed { index, it -> when (it.canonicalName) {
"float", "java.lang.Float" -> values[index].asFloat()
"double", "java.lang.Double" -> values[index].asDouble()
"byte", "java.lang.Byte" -> values[index].asByte()
"char", "java.lang.Character" -> values[index].asChar()
"short", "java.lang.Short" -> values[index].asShort()
"int", "java.lang.Integer" -> values[index].asInt()
"long", "java.lang.Long" -> values[index].asLong()
"boolean", "java.lang.Boolean" -> values[index].asBoolean()
"java.lang.String" -> values[index].asString()
else -> throw NotImplementedError("No conversion for ${it.canonicalName} exists")
} }.toTypedArray()
}
private fun <T> Array<T>.tail(): Array<T> = this.sliceArray(1..this.lastIndex)
} }

View File

@@ -2,12 +2,14 @@ package net.torvald.tsvm.peripheral
import com.badlogic.gdx.Gdx import com.badlogic.gdx.Gdx
import com.badlogic.gdx.backends.lwjgl3.audio.OpenALLwjgl3Audio import com.badlogic.gdx.backends.lwjgl3.audio.OpenALLwjgl3Audio
import com.badlogic.gdx.utils.GdxRuntimeException
import com.badlogic.gdx.utils.Queue import com.badlogic.gdx.utils.Queue
import net.torvald.UnsafeHelper import net.torvald.UnsafeHelper
import net.torvald.UnsafePtr import net.torvald.UnsafePtr
import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.toUint import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.toUint
import net.torvald.tsvm.ThreeFiveMiniUfloat import net.torvald.tsvm.ThreeFiveMiniUfloat
import net.torvald.tsvm.VM import net.torvald.tsvm.VM
import net.torvald.tsvm.getHashStr
private fun Boolean.toInt() = if (this) 1 else 0 private fun Boolean.toInt() = if (this) 1 else 0
@@ -17,14 +19,14 @@ private class RenderRunnable(val playhead: AudioAdapter.Playhead) : Runnable {
} }
@Volatile private var exit = false @Volatile private var exit = false
override fun run() { override fun run() {
while (!Thread.interrupted()) { while (!exit) {
if (playhead.isPcmMode) { if (playhead.isPcmMode) {
val writeQueue = playhead.pcmQueue val writeQueue = playhead.pcmQueue
if (playhead.isPlaying && writeQueue.notEmpty()) { if (playhead.isPlaying && writeQueue.notEmpty()) {
printdbg("Taking samples from queue (queue size: ${writeQueue.size})") printdbg("Taking samples from queue (queue size: ${writeQueue.size}/${playhead.getPcmQueueCapacity()})")
val samples = writeQueue.removeFirst() val samples = writeQueue.removeFirst()
playhead.position = writeQueue.size playhead.position = writeQueue.size
@@ -46,7 +48,7 @@ private class RenderRunnable(val playhead: AudioAdapter.Playhead) : Runnable {
Thread.sleep(1) Thread.sleep(1)
} }
Thread.currentThread().interrupt() playhead.audioDevice.dispose()
} }
fun stop() { fun stop() {
exit = true exit = true
@@ -59,10 +61,10 @@ private class WriteQueueingRunnable(val playhead: AudioAdapter.Playhead, val pcm
} }
@Volatile private var exit = false @Volatile private var exit = false
override fun run() { override fun run() {
while (!Thread.interrupted()) { while (!exit) {
playhead.let { playhead.let {
if (it.pcmQueue.size < it.getPcmQueueSize() && it.pcmUpload && it.pcmUploadLength > 0) { if (it.pcmQueue.size < it.getPcmQueueCapacity() && it.pcmUpload && it.pcmUploadLength > 0) {
printdbg("Downloading samples ${it.pcmUploadLength}") printdbg("Downloading samples ${it.pcmUploadLength}")
val samples = ByteArray(it.pcmUploadLength) val samples = ByteArray(it.pcmUploadLength)
@@ -77,7 +79,6 @@ private class WriteQueueingRunnable(val playhead: AudioAdapter.Playhead, val pcm
Thread.sleep(4) Thread.sleep(4)
} }
Thread.currentThread().interrupt()
} }
fun stop() { fun stop() {
exit = true exit = true
@@ -98,12 +99,12 @@ class AudioAdapter(val vm: VM) : PeriBase {
const val SAMPLING_RATE = 30000 const val SAMPLING_RATE = 30000
} }
internal val sampleBin = UnsafeHelper.allocate(114687L) internal val sampleBin = UnsafeHelper.allocate(114687L, this)
internal val instruments = Array(256) { TaudInst() } internal val instruments = Array(256) { TaudInst() }
internal val playdata = Array(256) { Array(64) { TaudPlayData(0,0,0,0,0,0,0,0) } } internal val playdata = Array(256) { Array(64) { TaudPlayData(0,0,0,0,0,0,0,0) } }
internal val playheads: Array<Playhead> internal val playheads: Array<Playhead>
internal val cueSheet = Array(2048) { PlayCue() } internal val cueSheet = Array(2048) { PlayCue() }
internal val pcmBin = UnsafeHelper.allocate(65536L) internal val pcmBin = UnsafeHelper.allocate(65536L, this)
private val renderRunnables: Array<RenderRunnable> private val renderRunnables: Array<RenderRunnable>
private val renderThreads: Array<Thread> private val renderThreads: Array<Thread>
@@ -114,6 +115,9 @@ class AudioAdapter(val vm: VM) : PeriBase {
throwable.printStackTrace() throwable.printStackTrace()
} }
val hash = getHashStr()
override fun toString() = "AudioAdapter!$hash"
init { init {
@@ -140,13 +144,14 @@ class AudioAdapter(val vm: VM) : PeriBase {
} }
Playhead(index = it, audioDevice = adev) Playhead(this, index = it, audioDevice = adev)
} }
renderRunnables = Array(4) { RenderRunnable(playheads[it]) } renderRunnables = Array(4) { RenderRunnable(playheads[it]) }
renderThreads = Array(4) { Thread(renderRunnables[it], "AudioRenderHead${it+1}") } renderThreads = Array(4) { Thread(renderRunnables[it], "AudioRenderHead${it+1}!$hash") }
writeQueueingRunnables = Array(4) { WriteQueueingRunnable(playheads[it], pcmBin) } writeQueueingRunnables = Array(4) { WriteQueueingRunnable(playheads[it], pcmBin) }
writeQueueingThreads = Array(4) { Thread(writeQueueingRunnables[it], "AudioQueueingHead${it+1}") } writeQueueingThreads = Array(4) { Thread(writeQueueingRunnables[it], "AudioQueueingHead${it+1}!$hash") }
// printdbg("AudioAdapter latency: ${audioDevice.latency}") // printdbg("AudioAdapter latency: ${audioDevice.latency}")
renderThreads.forEach { it.uncaughtExceptionHandler = threadExceptionHandler; it.start() } renderThreads.forEach { it.uncaughtExceptionHandler = threadExceptionHandler; it.start() }
@@ -290,6 +295,7 @@ class AudioAdapter(val vm: VM) : PeriBase {
internal object PlayInstNop : PlayInstruction(0) internal object PlayInstNop : PlayInstruction(0)
internal data class Playhead( internal data class Playhead(
private val parent: AudioAdapter,
val index: Int, val index: Int,
var position: Int = 0, var position: Int = 0,
@@ -372,10 +378,11 @@ class AudioAdapter(val vm: VM) : PeriBase {
} }
} }
fun getPcmQueueSize() = QUEUE_SIZE[pcmQueueSizeIndex] fun getPcmQueueCapacity() = QUEUE_SIZE[pcmQueueSizeIndex]
fun dispose() { fun dispose() {
audioDevice.dispose() println("AudioDevice dispose ${parent.renderThreads[index]}")
try { audioDevice.dispose() } catch (e: GdxRuntimeException) { println(" "+ e.message) }
} }
companion object { companion object {

View File

@@ -7,8 +7,10 @@ import com.badlogic.gdx.graphics.g2d.SpriteBatch
import com.badlogic.gdx.graphics.g2d.TextureRegion import com.badlogic.gdx.graphics.g2d.TextureRegion
import com.badlogic.gdx.graphics.glutils.FrameBuffer import com.badlogic.gdx.graphics.glutils.FrameBuffer
import com.badlogic.gdx.math.Matrix4 import com.badlogic.gdx.math.Matrix4
import com.badlogic.gdx.utils.Disposable
import com.badlogic.gdx.utils.GdxRuntimeException import com.badlogic.gdx.utils.GdxRuntimeException
import net.torvald.UnsafeHelper import net.torvald.UnsafeHelper
import net.torvald.UnsafePtr
import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.toUint import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.toUint
import net.torvald.tsvm.FBM import net.torvald.tsvm.FBM
import net.torvald.tsvm.LoadShader import net.torvald.tsvm.LoadShader
@@ -17,6 +19,7 @@ import net.torvald.tsvm.kB
import net.torvald.tsvm.peripheral.GraphicsAdapter.Companion.DRAW_SHADER_FRAG import net.torvald.tsvm.peripheral.GraphicsAdapter.Companion.DRAW_SHADER_FRAG
import java.io.InputStream import java.io.InputStream
import java.io.OutputStream import java.io.OutputStream
import java.lang.IllegalArgumentException
import kotlin.experimental.and import kotlin.experimental.and
data class AdapterConfig( data class AdapterConfig(
@@ -64,8 +67,8 @@ open class GraphicsAdapter(private val assetsRoot: String, val vm: VM, val confi
protected val theme = config.theme protected val theme = config.theme
protected val TAB_SIZE = 8 protected val TAB_SIZE = 8
internal val framebuffer = UnsafeHelper.allocate(WIDTH.toLong() * HEIGHT)//Pixmap(WIDTH, HEIGHT, Pixmap.Format.Alpha) internal val framebuffer = UnsafeHelper.allocate(WIDTH.toLong() * HEIGHT, this)//Pixmap(WIDTH, HEIGHT, Pixmap.Format.Alpha)
internal val framebuffer2 = if (sgr.bankCount >= 2) UnsafeHelper.allocate(WIDTH.toLong() * HEIGHT) else null internal val framebuffer2 = if (sgr.bankCount >= 2) UnsafeHelper.allocate(WIDTH.toLong() * HEIGHT, this) else null
internal val framebufferOut = Pixmap(WIDTH, HEIGHT, Pixmap.Format.RGBA8888) internal val framebufferOut = Pixmap(WIDTH, HEIGHT, Pixmap.Format.RGBA8888)
protected var rendertex = Texture(1, 1, Pixmap.Format.RGBA8888) protected var rendertex = Texture(1, 1, Pixmap.Format.RGBA8888)
internal val paletteOfFloats = FloatArray(1024) { internal val paletteOfFloats = FloatArray(1024) {
@@ -78,9 +81,9 @@ open class GraphicsAdapter(private val assetsRoot: String, val vm: VM, val confi
protected var chrrom0 = Texture(1,1,Pixmap.Format.RGBA8888) protected var chrrom0 = Texture(1,1,Pixmap.Format.RGBA8888)
protected val faketex: Texture protected val faketex: Texture
internal val textArea = UnsafeHelper.allocate(7682) internal val textArea = UnsafeHelper.allocate(7682, this)
internal val unusedArea = UnsafeHelper.allocate(1024) internal val unusedArea = UnsafeHelper.allocate(1024, this)
internal val scanlineOffsets = UnsafeHelper.allocate(1024) internal val scanlineOffsets = UnsafeHelper.allocate(1024, this)
protected val paletteShader = LoadShader(DRAW_SHADER_VERT, config.paletteShader) protected val paletteShader = LoadShader(DRAW_SHADER_VERT, config.paletteShader)
protected val textShader = LoadShader(DRAW_SHADER_VERT, config.fragShader) protected val textShader = LoadShader(DRAW_SHADER_VERT, config.fragShader)
@@ -124,7 +127,7 @@ open class GraphicsAdapter(private val assetsRoot: String, val vm: VM, val confi
// override var halfrowMode = false // override var halfrowMode = false
private val instArea = UnsafeHelper.allocate(65536L) private val instArea = UnsafeHelper.allocate(65536L, this)
override var rawCursorPos: Int override var rawCursorPos: Int
get() = textArea.getShortFree(memTextCursorPosOffset).toInt() get() = textArea.getShortFree(memTextCursorPosOffset).toInt()
@@ -943,27 +946,31 @@ open class GraphicsAdapter(private val assetsRoot: String, val vm: VM, val confi
} }
} }
fun Disposable.tryDispose() {
try { this.dispose() } catch (_: GdxRuntimeException) {} catch (_: IllegalArgumentException) {}
}
override fun dispose() { override fun dispose() {
//testTex.dispose() //testTex.dispose()
try { framebuffer.destroy() } catch (_: GdxRuntimeException) {} // paletteShader.tryDispose()
try { framebuffer2?.destroy() } catch (_: GdxRuntimeException) {} // textShader.tryDispose()
try { framebufferOut.dispose() } catch (_: GdxRuntimeException) {} framebuffer.destroy()
rendertex.dispose() framebuffer2?.destroy()
framebufferOut.tryDispose()
rendertex.tryDispose()
textArea.destroy() textArea.destroy()
textForePixmap.dispose() textForePixmap.tryDispose()
textBackPixmap.dispose() textBackPixmap.tryDispose()
textPixmap.dispose() textPixmap.tryDispose()
paletteShader.dispose() faketex.tryDispose()
textShader.dispose() // outFBOs.forEach { it.tryDispose() }
faketex.dispose() outFBObatch.tryDispose()
outFBOs.forEach { it.dispose() }
outFBObatch.dispose()
try { textForeTex.dispose() } catch (_: GdxRuntimeException) {} textForeTex.tryDispose()
try { textBackTex.dispose() } catch (_: GdxRuntimeException) {} textBackTex.tryDispose()
chrrom0.dispose() chrrom0.tryDispose()
chrrom.dispose() chrrom.tryDispose()
unusedArea.destroy() unusedArea.destroy()
scanlineOffsets.destroy() scanlineOffsets.destroy()
instArea.destroy() instArea.destroy()

View File

@@ -27,20 +27,20 @@ class IOSpace(val vm: VM) : PeriBase, InputProcessor {
private val keyboardBuffer = CircularArray<Byte>(32, true) private val keyboardBuffer = CircularArray<Byte>(32, true)
internal val blockTransferRx = arrayOf( internal val blockTransferRx = arrayOf(
UnsafeHelper.allocate(4096), UnsafeHelper.allocate(4096, this),
UnsafeHelper.allocate(4096), UnsafeHelper.allocate(4096, this),
UnsafeHelper.allocate(4096), UnsafeHelper.allocate(4096, this),
UnsafeHelper.allocate(4096) UnsafeHelper.allocate(4096, this)
) )
internal val blockTransferTx = arrayOf( internal val blockTransferTx = arrayOf(
UnsafeHelper.allocate(4096), UnsafeHelper.allocate(4096, this),
UnsafeHelper.allocate(4096), UnsafeHelper.allocate(4096, this),
UnsafeHelper.allocate(4096), UnsafeHelper.allocate(4096, this),
UnsafeHelper.allocate(4096) UnsafeHelper.allocate(4096, this)
) )
/*private*/ val blockTransferPorts = Array(4) { BlockTransferPort(vm, it) } /*private*/ val blockTransferPorts = Array(4) { BlockTransferPort(vm, it) }
private val peripheralFast = UnsafeHelper.allocate(1024) private val peripheralFast = UnsafeHelper.allocate(1024, this)
private val keyEventBuffers = ByteArray(8) private val keyEventBuffers = ByteArray(8)

View File

@@ -1,7 +1,6 @@
package net.torvald.tsvm.peripheral package net.torvald.tsvm.peripheral
import com.badlogic.gdx.audio.AudioDevice import com.badlogic.gdx.audio.AudioDevice
import com.badlogic.gdx.backends.lwjgl3.audio.OpenALAudioDevice
import com.badlogic.gdx.backends.lwjgl3.audio.OpenALLwjgl3Audio import com.badlogic.gdx.backends.lwjgl3.audio.OpenALLwjgl3Audio
import com.badlogic.gdx.math.MathUtils import com.badlogic.gdx.math.MathUtils
import com.badlogic.gdx.utils.GdxRuntimeException import com.badlogic.gdx.utils.GdxRuntimeException
@@ -120,6 +119,14 @@ class OpenALBufferedAudioDevice(
freeSourceMethod.invoke(audio, sourceID) freeSourceMethod.invoke(audio, sourceID)
} }
private val alErrors = hashMapOf(
AL10.AL_INVALID_NAME to "AL_INVALID_NAME",
AL10.AL_INVALID_ENUM to "AL_INVALID_ENUM",
AL10.AL_INVALID_VALUE to "AL_INVALID_VALUE",
AL10.AL_INVALID_OPERATION to "AL_INVALID_OPERATION",
AL10.AL_OUT_OF_MEMORY to "AL_OUT_OF_MEMORY"
)
fun writeSamples(data: ByteArray, offset: Int, length: Int) { fun writeSamples(data: ByteArray, offset: Int, length: Int) {
var offset = offset var offset = offset
var length = length var length = length
@@ -129,8 +136,11 @@ class OpenALBufferedAudioDevice(
if (sourceID == -1) return if (sourceID == -1) return
if (buffers == null) { if (buffers == null) {
buffers = BufferUtils.createIntBuffer(bufferCount) buffers = BufferUtils.createIntBuffer(bufferCount)
AL10.alGetError()
AL10.alGenBuffers(buffers) AL10.alGenBuffers(buffers)
if (AL10.alGetError() != AL10.AL_NO_ERROR) throw GdxRuntimeException("Unabe to allocate audio buffers.") AL10.alGetError().let {
if (it != AL10.AL_NO_ERROR) throw GdxRuntimeException("Unabe to allocate audio buffers: ${alErrors[it]}")
}
} }
AL10.alSourcei(sourceID, AL10.AL_LOOPING, AL10.AL_FALSE) AL10.alSourcei(sourceID, AL10.AL_LOOPING, AL10.AL_FALSE)
AL10.alSourcef(sourceID, AL10.AL_GAIN, volume) AL10.alSourcef(sourceID, AL10.AL_GAIN, volume)

View File

@@ -19,7 +19,7 @@ open class RamBank(val vm: VM, bankCount: Int) : PeriBase {
if (banks % 2 == 1) banks += 1 if (banks % 2 == 1) banks += 1
} }
internal val mem = UnsafeHelper.allocate(bankSize * banks) internal val mem = UnsafeHelper.allocate(bankSize * banks, this)
protected var map0 = 0 protected var map0 = 0
protected var map1 = 1 protected var map1 = 1

View File

@@ -16,7 +16,7 @@ class TTY(assetsRoot: String, val vm: VM) : GlassTty(TEXT_ROWS, TEXT_COLS), Peri
} }
private val chrrom = Texture("$assetsRoot/tty.png") private val chrrom = Texture("$assetsRoot/tty.png")
private val textBuffer = UnsafeHelper.allocate(TEXT_ROWS * TEXT_COLS * 2L) private val textBuffer = UnsafeHelper.allocate(TEXT_ROWS * TEXT_COLS * 2L, this)
override var rawCursorPos = 0 override var rawCursorPos = 0
private val TEXT_AREA_SIZE = TEXT_COLS * TEXT_ROWS private val TEXT_AREA_SIZE = TEXT_COLS * TEXT_ROWS

View File

@@ -124,8 +124,8 @@ class Videotron2K(var gpu: GraphicsAdapter?) {
""".trimIndent() """.trimIndent()
} }
internal var regs = UnsafeHelper.allocate(16 * 4) internal var regs = UnsafeHelper.allocate(16 * 4, this)
internal var internalMem = UnsafeHelper.allocate(16384) // internal var internalMem = UnsafeHelper.allocate(16384, this)
internal var callStack = Stack<Pair<Long, Int>>() // Pair of Scene-ID (has SCENE_PREFIX; 0 with no prefix for root scene) and ProgramCounter internal var callStack = Stack<Pair<Long, Int>>() // Pair of Scene-ID (has SCENE_PREFIX; 0 with no prefix for root scene) and ProgramCounter
/* Compile-time variables */ /* Compile-time variables */

View File

@@ -87,7 +87,7 @@ class ProfilesMenu(parent: VMEmuExecutable, x: Int, y: Int, w: Int, h: Int) : Em
parent.killVMenv(theVM) parent.killVMenv(theVM)
} }
else if (mx in 395..415 && !theVM.isRunning) { else if (mx in 395..415 && !theVM.isRunning) {
parent.initVMenv(theVM) parent.initVMenv(theVM, profileName)
} }
} }
} }

View File

@@ -68,7 +68,7 @@ class VMEmuExecutable(val windowWidth: Int, val windowHeight: Int, var panelsX:
val watchdogs = hashMapOf<String, VMWatchdog>("TEVD_SYNC" to TEVD_SYNC) val watchdogs = hashMapOf<String, VMWatchdog>("TEVD_SYNC" to TEVD_SYNC)
data class VMRunnerInfo(val vm: VM, val name: String) data class VMRunnerInfo(val vm: VM, val profileName: String)
private val vms = arrayOfNulls<VMRunnerInfo>(this.panelsX * this.panelsY - 1) // index: # of the window where the reboot was requested private val vms = arrayOfNulls<VMRunnerInfo>(this.panelsX * this.panelsY - 1) // index: # of the window where the reboot was requested
@@ -78,8 +78,8 @@ class VMEmuExecutable(val windowWidth: Int, val windowHeight: Int, var panelsX:
lateinit var fbatch: FlippingSpriteBatch lateinit var fbatch: FlippingSpriteBatch
lateinit var camera: OrthographicCamera lateinit var camera: OrthographicCamera
var vmRunners = HashMap<Int, VMRunner>() // <VM's identifier, VMRunner> var vmRunners = HashMap<VmId, VMRunner>() // <VM's identifier, VMRunner>
var coroutineJobs = HashMap<Int, Job>() // <VM's identifier, Job> var coroutineJobs = HashMap<VmId, Job>() // <VM's identifier, Job>
companion object { companion object {
val APPDATADIR = TsvmEmulator.defaultDir val APPDATADIR = TsvmEmulator.defaultDir
@@ -100,7 +100,7 @@ class VMEmuExecutable(val windowWidth: Int, val windowHeight: Int, var panelsX:
private val currentlyLoadedProfiles = HashMap<String, VM>() private val currentlyLoadedProfiles = HashMap<String, VM>()
internal fun getVMbyProfileName(name: String): VM? { internal fun getVMbyProfileName(name: String): VM? {
if (profiles.containsKey(name)) { if (profiles.containsKey(name)) {
return currentlyLoadedProfiles.getOrPut(name) { _makeVMfromJson(profiles[name]!!, name) } return currentlyLoadedProfiles.getOrPut(name) { makeVMfromJson(profiles[name]!!, name) }
} }
else else
return null return null
@@ -177,7 +177,7 @@ class VMEmuExecutable(val windowWidth: Int, val windowHeight: Int, var panelsX:
vms[0] = VMRunnerInfo(vm, "Initial VM")*/ vms[0] = VMRunnerInfo(vm, "Initial VM")*/
val vm1 = getVMbyProfileName("Initial VM")!! val vm1 = getVMbyProfileName("Initial VM")!!
initVMenv(vm1) initVMenv(vm1, "Initial VM")
vms[0] = VMRunnerInfo(vm1, "Initial VM") vms[0] = VMRunnerInfo(vm1, "Initial VM")
init() init()
@@ -195,9 +195,9 @@ class VMEmuExecutable(val windowWidth: Int, val windowHeight: Int, var panelsX:
Gdx.input.inputProcessor = if (currentVMselection != null) vms[currentVMselection!!]?.vm?.getIO() ?: null else vmEmuInputProcessor Gdx.input.inputProcessor = if (currentVMselection != null) vms[currentVMselection!!]?.vm?.getIO() ?: null else vmEmuInputProcessor
} }
internal fun initVMenv(vm: VM) { internal fun initVMenv(vm: VM, profileName: String) {
val gpu = ReferenceGraphicsAdapter2("./assets", vm) val gpu = ReferenceGraphicsAdapter2("./assets", vm)
VMSetupBroker.initVMenv(vm, gpu, vmRunners, coroutineJobs) { VMSetupBroker.initVMenv(vm, profiles[profileName]!!, profileName, gpu, vmRunners, coroutineJobs) {
it.printStackTrace() it.printStackTrace()
VMSetupBroker.killVMenv(vm, vmRunners, coroutineJobs) VMSetupBroker.killVMenv(vm, vmRunners, coroutineJobs)
} }
@@ -258,12 +258,14 @@ class VMEmuExecutable(val windowWidth: Int, val windowHeight: Int, var panelsX:
watchdogs.forEach { (_, watchdog) -> watchdog.update(dt) } watchdogs.forEach { (_, watchdog) -> watchdog.update(dt) }
} }
private fun reboot(vm: VM) { private fun reboot(profileName: String) {
val vm = currentlyLoadedProfiles[profileName]!!
vmRunners[vm.id]!!.close() vmRunners[vm.id]!!.close()
coroutineJobs[vm.id]!!.cancel("reboot requested") coroutineJobs[vm.id]!!.cancel("reboot requested")
vm.init() vm.init()
initVMenv(vm) initVMenv(vm, profileName)
} }
private fun updateGame(delta: Float) { private fun updateGame(delta: Float) {
@@ -279,7 +281,7 @@ class VMEmuExecutable(val windowWidth: Int, val windowHeight: Int, var panelsX:
} }
vms.forEachIndexed { index, it -> vms.forEachIndexed { index, it ->
if (it?.vm?.resetDown == true && index == currentVMselection) { reboot(it.vm) } if (it?.vm?.resetDown == true && index == currentVMselection) { reboot(it.profileName) }
if (it?.vm?.isRunning == true) it?.vm?.update(delta) if (it?.vm?.isRunning == true) it?.vm?.update(delta)
} }
@@ -302,7 +304,7 @@ class VMEmuExecutable(val windowWidth: Int, val windowHeight: Int, var panelsX:
it.fillRect(xoff, yoff, 2, windowHeight) it.fillRect(xoff, yoff, 2, windowHeight)
it.fillRect(xoff + windowWidth - 2, yoff, 2, windowHeight) it.fillRect(xoff + windowWidth - 2, yoff, 2, windowHeight)
vmInfo?.name?.let { name -> vmInfo?.profileName?.let { name ->
it.fillRect(xoff, yoff, (name.length + 2) * FONT.W, FONT.H) it.fillRect(xoff, yoff, (name.length + 2) * FONT.W, FONT.H)
it.color = if (index == currentVMselection) EmulatorGuiToolkit.Theme.COL_ACTIVE else EmulatorGuiToolkit.Theme.COL_ACTIVE2 it.color = if (index == currentVMselection) EmulatorGuiToolkit.Theme.COL_ACTIVE else EmulatorGuiToolkit.Theme.COL_ACTIVE2
FONT.draw(it, name, xoff + FONT.W.toFloat(), yoff.toFloat()) FONT.draw(it, name, xoff + FONT.W.toFloat(), yoff.toFloat())
@@ -501,7 +503,6 @@ class VMEmuExecutable(val windowWidth: Int, val windowHeight: Int, var panelsX:
} }
""".trimIndent() """.trimIndent()
/** /**
* You'll want to further init the things using the VM this function returns, such as: * You'll want to further init the things using the VM this function returns, such as:
* *
@@ -512,9 +513,9 @@ class VMEmuExecutable(val windowWidth: Int, val windowHeight: Int, var panelsX:
* } * }
* ``` * ```
*/ */
private fun _makeVMfromJson(json: JsonValue, profileName: String): VM { private fun makeVMfromJson(json: JsonValue, profileName: String): VM {
println("Processing profile '$profileName'") println("Processing profile '$profileName'")
val assetsDir = json.getString("assetsdir") val assetsDir = json.getString("assetsdir")
val ramsize = json.getLong("ramsize") val ramsize = json.getLong("ramsize")
val cardslots = json.getInt("cardslots") val cardslots = json.getInt("cardslots")
@@ -522,63 +523,6 @@ class VMEmuExecutable(val windowWidth: Int, val windowHeight: Int, var panelsX:
val vm = VM(assetsDir, ramsize, TheRealWorld(), roms, cardslots, watchdogs) val vm = VM(assetsDir, ramsize, TheRealWorld(), roms, cardslots, watchdogs)
// install peripherals
listOf("com1", "com2", "com3", "com4").map { json.get(it) }.forEachIndexed { index, jsonValue ->
jsonValue?.let { deviceInfo ->
val className = deviceInfo.getString("cls")
val loadedClass = Class.forName(className)
val argTypess = loadedClass.declaredConstructors
var successful = false
var k = 0
// just try out all the possible argTypes
while (!successful && k < argTypess.size) {
try {
val argTypes = argTypess[k].parameterTypes
println("loadedClass = $className")
println("trying constructor args[${k}/${argTypess.lastIndex}]: ${argTypes.joinToString { it.canonicalName }}")
val args = deviceInfo.get("args").allIntoJavaType(argTypes.tail())
val loadedClassConstructor = loadedClass.getConstructor(*argTypes)
val loadedClassInstance = loadedClassConstructor.newInstance(vm, *args)
vm.getIO().blockTransferPorts[index].attachDevice(loadedClassInstance as BlockTransferInterface)
println("COM${index+1} = ${loadedClassInstance.javaClass.canonicalName}: ${args.joinToString()}")
successful = true
}
catch (e: IllegalArgumentException) {
// e.printStackTrace()
}
finally {
k += 1
}
}
if (!successful) {
throw RuntimeException("Invalid or insufficient arguments for $className in the profile $profileName")
}
}
}
(2..cardslots).map { it to json.get("card$it") }.forEach { (index, jsonValue) ->
jsonValue?.let { deviceInfo ->
val className = deviceInfo.getString("cls")
val loadedClass = Class.forName(className)
val argTypes = loadedClass.declaredConstructors[0].parameterTypes
val args = deviceInfo.get("args").allIntoJavaType(argTypes.tail())
val loadedClassConstructor = loadedClass.getConstructor(*argTypes)
val loadedClassInstance = loadedClassConstructor.newInstance(vm, *args)
val peri = loadedClassInstance as PeriBase
vm.peripheralTable[index] = PeripheralEntry(
peri
)
}
}
return vm return vm
} }