From ceddf2c5b93e4b6bfcf5f79043a2e7e1a0f62614 Mon Sep 17 00:00:00 2001 From: minjaesong Date: Wed, 4 Jan 2023 19:09:42 +0900 Subject: [PATCH] improved way of initialising vms at (re)launch --- assets/disk0/home/soundtest.js | 10 +- assets/disk0/tvdos/TVDOS.SYS | 2 +- assets/disk0/tvdos/bin/encodemov.js | 60 +++++----- assets/disk0/tvdos/bin/playmov.js | 15 ++- .../net/torvald/tsvm/AudioJSR223Delegate.kt | 12 +- .../torvald/tsvm/GraphicsJSR223Delegate.kt | 2 +- tsvm_core/src/net/torvald/tsvm/UnsafePtr.kt | 8 +- tsvm_core/src/net/torvald/tsvm/Utils.kt | 6 + tsvm_core/src/net/torvald/tsvm/VM.kt | 10 +- .../src/net/torvald/tsvm/VMJSR223Delegate.kt | 2 + .../src/net/torvald/tsvm/VMSetupBroker.kt | 113 +++++++++++++++++- .../torvald/tsvm/peripheral/AudioAdapter.kt | 33 +++-- .../tsvm/peripheral/GraphicsAdapter.kt | 51 ++++---- .../net/torvald/tsvm/peripheral/IOSpace.kt | 18 +-- .../peripheral/OpenALBufferedAudioDevice.kt | 14 ++- .../net/torvald/tsvm/peripheral/RamBank.kt | 2 +- .../src/net/torvald/tsvm/peripheral/TTY.kt | 2 +- .../src/net/torvald/tsvm/vdc/Videotron2K.kt | 4 +- .../src/net/torvald/tsvm/ProfilesMenu.kt | 2 +- .../src/net/torvald/tsvm/VMEmuExecutable.kt | 86 +++---------- 20 files changed, 275 insertions(+), 177 deletions(-) create mode 100644 tsvm_core/src/net/torvald/tsvm/Utils.kt diff --git a/assets/disk0/home/soundtest.js b/assets/disk0/home/soundtest.js index 44bf7d0..41b3d31 100644 --- a/assets/disk0/home/soundtest.js +++ b/assets/disk0/home/soundtest.js @@ -38,8 +38,8 @@ if (statusCode != 0) { let readCount = 0 -function readBytes(length) { - let ptr = sys.malloc(length) +function readBytes(length, ptrToDecode) { + let ptr = (ptrToDecode === undefined) ? sys.malloc(length) : ptrToDecode let requiredBlocks = Math.floor((readCount + length) / 4096) - Math.floor(readCount / 4096) 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 // briefly manifests, even if you're queueing only once +const decodePtr = sys.malloc(BLOCK_SIZE) + while (sampleSize > 0) { let queueSize = audio.getPosition(0) @@ -123,14 +125,13 @@ while (sampleSize > 0) { // upload four samples for lag-safely for (let repeat = QUEUE_MAX - queueSize; repeat > 0; repeat--) { let readLength = (sampleSize < BLOCK_SIZE) ? sampleSize : BLOCK_SIZE - let samples = readBytes(readLength) + let samples = readBytes(readLength, decodePtr) audio.putPcmDataByPtr(samples, readLength, 0) audio.setSampleUploadLength(0, readLength) audio.startSampleUpload(0) sampleSize -= readLength - sys.free(samples) 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 +sys.free(samples) diff --git a/assets/disk0/tvdos/TVDOS.SYS b/assets/disk0/tvdos/TVDOS.SYS index 63fc64b..960120e 100644 --- a/assets/disk0/tvdos/TVDOS.SYS +++ b/assets/disk0/tvdos/TVDOS.SYS @@ -1094,7 +1094,7 @@ var execApp = (cmdsrc, args, appname) => { /////////////////////////////////////////////////////////////////////////////// // Boot script -serial.println("TVDOS.SYS initialised, running boot script..."); +serial.println(`TVDOS.SYS initialised on VM ${sys.getVmId()}, running boot script...`); var _G = {}; let cmdfile = files.open("A:/tvdos/bin/command.js") eval(`var _AUTOEXEC=function(exec_args){${cmdfile.sread()}\n};` + diff --git a/assets/disk0/tvdos/bin/encodemov.js b/assets/disk0/tvdos/bin/encodemov.js index 1bab813..5ab96b7 100644 --- a/assets/disk0/tvdos/bin/encodemov.js +++ b/assets/disk0/tvdos/bin/encodemov.js @@ -74,36 +74,6 @@ for (let f = 1; ; f++) { // insert sync packet 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 if (audioRemaining > 0) { @@ -136,6 +106,36 @@ for (let f = 1; ; f++) { 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 (f > TOTAL_FRAMES && audioRemaining <= 0) break diff --git a/assets/disk0/tvdos/bin/playmov.js b/assets/disk0/tvdos/bin/playmov.js index 2c9e6cd..a38c51c 100644 --- a/assets/disk0/tvdos/bin/playmov.js +++ b/assets/disk0/tvdos/bin/playmov.js @@ -29,8 +29,8 @@ con.clear(); con.curs_set(0) let readCount = 0 -function readBytes(length) { - let ptr = sys.malloc(length) +function readBytes(length, ptrToDecode) { + let ptr = (ptrToDecode === undefined) ? sys.malloc(length) : ptrToDecode let requiredBlocks = Math.floor((readCount + length) / 4096) - Math.floor(readCount / 4096) let completedReads = 0 @@ -156,8 +156,8 @@ let ipfbuf = sys.malloc(FBUF_SIZE) graphics.setGraphicsMode(4) let startTime = sys.nanoTime() - let framesRead = 0 +let audioFired = false audio.resetParams(0) audio.purgeQueue(0) @@ -182,6 +182,8 @@ while (readCount < FILE_LENGTH) { let packetType = readShort() + // ideally, first two packets will be audio packets + // sync packets if (65535 == packetType) { frameUnit -= 1 @@ -204,6 +206,12 @@ while (readCount < FILE_LENGTH) { if (frameUnit == 1) { gzip.decompFromTo(gzippedPtr, payloadLen, ipfbuf) // should return FBUF_SIZE 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) @@ -221,7 +229,6 @@ while (readCount < FILE_LENGTH) { audio.putPcmDataByPtr(samples, readLength, 0) audio.setSampleUploadLength(0, readLength) audio.startSampleUpload(0) - audio.play(0) sys.free(samples) } diff --git a/tsvm_core/src/net/torvald/tsvm/AudioJSR223Delegate.kt b/tsvm_core/src/net/torvald/tsvm/AudioJSR223Delegate.kt index 646aa86..d312f68 100644 --- a/tsvm_core/src/net/torvald/tsvm/AudioJSR223Delegate.kt +++ b/tsvm_core/src/net/torvald/tsvm/AudioJSR223Delegate.kt @@ -7,7 +7,11 @@ import net.torvald.tsvm.peripheral.AudioAdapter */ 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) 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 setPcmQueueSizeIndex(playhead: Int, index: Int) { getPlayhead(playhead)?.pcmQueueSizeIndex = index } - fun getPcmQueueSizeIndex(playhead: Int, index: Int) { getPlayhead(playhead)?.pcmQueueSizeIndex } - fun getPcmQueueSize(playhead: Int, index: Int) { getPlayhead(playhead)?.getPcmQueueSize() } + fun setPcmQueueCapacityIndex(playhead: Int, index: Int) { getPlayhead(playhead)?.pcmQueueSizeIndex = index } + fun getPcmQueueCapacityIndex(playhead: Int, index: Int) { getPlayhead(playhead)?.pcmQueueSizeIndex } + fun getPcmQueueCapacity(playhead: Int, index: Int) { getPlayhead(playhead)?.getPcmQueueCapacity() } fun resetParams(playhead: Int) { getPlayhead(playhead)?.resetParams() diff --git a/tsvm_core/src/net/torvald/tsvm/GraphicsJSR223Delegate.kt b/tsvm_core/src/net/torvald/tsvm/GraphicsJSR223Delegate.kt index 2f886fd..543f573 100644 --- a/tsvm_core/src/net/torvald/tsvm/GraphicsJSR223Delegate.kt +++ b/tsvm_core/src/net/torvald/tsvm/GraphicsJSR223Delegate.kt @@ -465,7 +465,7 @@ class GraphicsJSR223Delegate(private val vm: VM) { } } 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) { srcimg.setFloat(channels * k + 0, vm.peek(srcPtr + channels * k + 0)!!.toUint().toFloat() / 255f) diff --git a/tsvm_core/src/net/torvald/tsvm/UnsafePtr.kt b/tsvm_core/src/net/torvald/tsvm/UnsafePtr.kt index c03178e..9b6696e 100644 --- a/tsvm_core/src/net/torvald/tsvm/UnsafePtr.kt +++ b/tsvm_core/src/net/torvald/tsvm/UnsafePtr.kt @@ -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. */ - fun allocate(size: Long): UnsafePtr { + fun allocate(size: Long, caller: Any): UnsafePtr { 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) = @@ -58,7 +58,7 @@ internal object UnsafeHelper { * * 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 private set @@ -162,7 +162,7 @@ internal class UnsafePtr(pointer: Long, allocSize: Long) { 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 inline fun printStackTrace(obj: Any) = printStackTrace(obj, System.out) // because of Java diff --git a/tsvm_core/src/net/torvald/tsvm/Utils.kt b/tsvm_core/src/net/torvald/tsvm/Utils.kt new file mode 100644 index 0000000..7dcb48f --- /dev/null +++ b/tsvm_core/src/net/torvald/tsvm/Utils.kt @@ -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("") \ No newline at end of file diff --git a/tsvm_core/src/net/torvald/tsvm/VM.kt b/tsvm_core/src/net/torvald/tsvm/VM.kt index feb70af..f682eab 100644 --- a/tsvm_core/src/net/torvald/tsvm/VM.kt +++ b/tsvm_core/src/net/torvald/tsvm/VM.kt @@ -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}") +inline class VmId(val text: String) { + override fun toString() = text +} + /** * A class representing an instance of a Virtual Machine @@ -30,7 +34,9 @@ class VM( 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() @@ -38,7 +44,7 @@ class VM( val MALLOC_UNIT = 64 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() } diff --git a/tsvm_core/src/net/torvald/tsvm/VMJSR223Delegate.kt b/tsvm_core/src/net/torvald/tsvm/VMJSR223Delegate.kt index ac3bf8a..ec14b20 100644 --- a/tsvm_core/src/net/torvald/tsvm/VMJSR223Delegate.kt +++ b/tsvm_core/src/net/torvald/tsvm/VMJSR223Delegate.kt @@ -13,6 +13,8 @@ import java.nio.charset.Charset */ class VMJSR223Delegate(private val vm: VM) { + fun getVmId() = vm.id.toString() + fun poke(addr: Int, value: Int) = vm.poke(addr.toLong(), value.toByte()) fun peek(addr: Int) = vm.peek(addr.toLong())!!.toInt().and(255) fun nanoTime() = System.nanoTime() diff --git a/tsvm_core/src/net/torvald/tsvm/VMSetupBroker.kt b/tsvm_core/src/net/torvald/tsvm/VMSetupBroker.kt index 5e0bbd2..2cb8873 100644 --- a/tsvm_core/src/net/torvald/tsvm/VMSetupBroker.kt +++ b/tsvm_core/src/net/torvald/tsvm/VMSetupBroker.kt @@ -2,11 +2,15 @@ package net.torvald.tsvm import com.badlogic.gdx.Gdx import com.badlogic.gdx.utils.GdxRuntimeException +import com.badlogic.gdx.utils.JsonValue import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.Job import kotlinx.coroutines.cancel import kotlinx.coroutines.launch +import net.torvald.tsvm.peripheral.BlockTransferInterface 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. @@ -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 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, coroutineJobs: HashMap, whatToDoOnVmException: (Throwable) -> Unit) { + fun initVMenv(vm: VM, profileJson: JsonValue, profileName: String, gpu: GraphicsAdapter, vmRunners: HashMap, coroutineJobs: HashMap, whatToDoOnVmException: (Throwable) -> Unit) { vm.init() try { @@ -29,6 +33,8 @@ object VMSetupBroker { } catch (_: GdxRuntimeException) {} // pixmap already disposed + installPeripherals(vm, profileJson, profileName) + vm.peripheralTable[1] = PeripheralEntry(gpu)//, GraphicsAdapter.VRAM_SIZE, 16, 0) 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 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, coroutineJobs: HashMap) { + fun killVMenv(vm: VM, vmRunners: HashMap, coroutineJobs: HashMap) { + vm.park() + vm.poke(-90L, -128) for (i in 1 until vm.peripheralTable.size) { try { @@ -64,13 +72,108 @@ object VMSetupBroker { catch (_: Throwable) {} } + coroutineJobs[vm.id]?.cancel("VM kill command received") + vmRunners[vm.id]?.close() + vm.getPrintStream = { TODO() } vm.getErrorStream = { 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>): Array { + 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 Array.tail(): Array = this.sliceArray(1..this.lastIndex) + } \ No newline at end of file diff --git a/tsvm_core/src/net/torvald/tsvm/peripheral/AudioAdapter.kt b/tsvm_core/src/net/torvald/tsvm/peripheral/AudioAdapter.kt index 972edd5..b312493 100644 --- a/tsvm_core/src/net/torvald/tsvm/peripheral/AudioAdapter.kt +++ b/tsvm_core/src/net/torvald/tsvm/peripheral/AudioAdapter.kt @@ -2,12 +2,14 @@ package net.torvald.tsvm.peripheral import com.badlogic.gdx.Gdx import com.badlogic.gdx.backends.lwjgl3.audio.OpenALLwjgl3Audio +import com.badlogic.gdx.utils.GdxRuntimeException import com.badlogic.gdx.utils.Queue import net.torvald.UnsafeHelper import net.torvald.UnsafePtr import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.toUint import net.torvald.tsvm.ThreeFiveMiniUfloat import net.torvald.tsvm.VM +import net.torvald.tsvm.getHashStr 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 override fun run() { - while (!Thread.interrupted()) { + while (!exit) { if (playhead.isPcmMode) { val writeQueue = playhead.pcmQueue 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() playhead.position = writeQueue.size @@ -46,7 +48,7 @@ private class RenderRunnable(val playhead: AudioAdapter.Playhead) : Runnable { Thread.sleep(1) } - Thread.currentThread().interrupt() + playhead.audioDevice.dispose() } fun stop() { exit = true @@ -59,10 +61,10 @@ private class WriteQueueingRunnable(val playhead: AudioAdapter.Playhead, val pcm } @Volatile private var exit = false override fun run() { - while (!Thread.interrupted()) { + while (!exit) { 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}") val samples = ByteArray(it.pcmUploadLength) @@ -77,7 +79,6 @@ private class WriteQueueingRunnable(val playhead: AudioAdapter.Playhead, val pcm Thread.sleep(4) } - Thread.currentThread().interrupt() } fun stop() { exit = true @@ -98,12 +99,12 @@ class AudioAdapter(val vm: VM) : PeriBase { 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 playdata = Array(256) { Array(64) { TaudPlayData(0,0,0,0,0,0,0,0) } } internal val playheads: Array internal val cueSheet = Array(2048) { PlayCue() } - internal val pcmBin = UnsafeHelper.allocate(65536L) + internal val pcmBin = UnsafeHelper.allocate(65536L, this) private val renderRunnables: Array private val renderThreads: Array @@ -114,6 +115,9 @@ class AudioAdapter(val vm: VM) : PeriBase { throwable.printStackTrace() } + val hash = getHashStr() + + override fun toString() = "AudioAdapter!$hash" 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]) } - 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) } - writeQueueingThreads = Array(4) { Thread(writeQueueingRunnables[it], "AudioQueueingHead${it+1}") } + writeQueueingThreads = Array(4) { Thread(writeQueueingRunnables[it], "AudioQueueingHead${it+1}!$hash") } // printdbg("AudioAdapter latency: ${audioDevice.latency}") renderThreads.forEach { it.uncaughtExceptionHandler = threadExceptionHandler; it.start() } @@ -290,6 +295,7 @@ class AudioAdapter(val vm: VM) : PeriBase { internal object PlayInstNop : PlayInstruction(0) internal data class Playhead( + private val parent: AudioAdapter, val index: Int, 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() { - audioDevice.dispose() + println("AudioDevice dispose ${parent.renderThreads[index]}") + try { audioDevice.dispose() } catch (e: GdxRuntimeException) { println(" "+ e.message) } } companion object { diff --git a/tsvm_core/src/net/torvald/tsvm/peripheral/GraphicsAdapter.kt b/tsvm_core/src/net/torvald/tsvm/peripheral/GraphicsAdapter.kt index 32746a5..44f6b38 100644 --- a/tsvm_core/src/net/torvald/tsvm/peripheral/GraphicsAdapter.kt +++ b/tsvm_core/src/net/torvald/tsvm/peripheral/GraphicsAdapter.kt @@ -7,8 +7,10 @@ import com.badlogic.gdx.graphics.g2d.SpriteBatch import com.badlogic.gdx.graphics.g2d.TextureRegion import com.badlogic.gdx.graphics.glutils.FrameBuffer import com.badlogic.gdx.math.Matrix4 +import com.badlogic.gdx.utils.Disposable import com.badlogic.gdx.utils.GdxRuntimeException import net.torvald.UnsafeHelper +import net.torvald.UnsafePtr import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.toUint import net.torvald.tsvm.FBM 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 java.io.InputStream import java.io.OutputStream +import java.lang.IllegalArgumentException import kotlin.experimental.and 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 TAB_SIZE = 8 - internal val framebuffer = UnsafeHelper.allocate(WIDTH.toLong() * HEIGHT)//Pixmap(WIDTH, HEIGHT, Pixmap.Format.Alpha) - internal val framebuffer2 = if (sgr.bankCount >= 2) UnsafeHelper.allocate(WIDTH.toLong() * HEIGHT) else null + 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, this) else null internal val framebufferOut = Pixmap(WIDTH, HEIGHT, Pixmap.Format.RGBA8888) protected var rendertex = Texture(1, 1, Pixmap.Format.RGBA8888) 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 val faketex: Texture - internal val textArea = UnsafeHelper.allocate(7682) - internal val unusedArea = UnsafeHelper.allocate(1024) - internal val scanlineOffsets = UnsafeHelper.allocate(1024) + internal val textArea = UnsafeHelper.allocate(7682, this) + internal val unusedArea = UnsafeHelper.allocate(1024, this) + internal val scanlineOffsets = UnsafeHelper.allocate(1024, this) protected val paletteShader = LoadShader(DRAW_SHADER_VERT, config.paletteShader) 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 - private val instArea = UnsafeHelper.allocate(65536L) + private val instArea = UnsafeHelper.allocate(65536L, this) override var rawCursorPos: Int 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() { //testTex.dispose() - try { framebuffer.destroy() } catch (_: GdxRuntimeException) {} - try { framebuffer2?.destroy() } catch (_: GdxRuntimeException) {} - try { framebufferOut.dispose() } catch (_: GdxRuntimeException) {} - rendertex.dispose() +// paletteShader.tryDispose() +// textShader.tryDispose() + framebuffer.destroy() + framebuffer2?.destroy() + framebufferOut.tryDispose() + rendertex.tryDispose() textArea.destroy() - textForePixmap.dispose() - textBackPixmap.dispose() - textPixmap.dispose() - paletteShader.dispose() - textShader.dispose() - faketex.dispose() - outFBOs.forEach { it.dispose() } - outFBObatch.dispose() + textForePixmap.tryDispose() + textBackPixmap.tryDispose() + textPixmap.tryDispose() + faketex.tryDispose() +// outFBOs.forEach { it.tryDispose() } + outFBObatch.tryDispose() - try { textForeTex.dispose() } catch (_: GdxRuntimeException) {} - try { textBackTex.dispose() } catch (_: GdxRuntimeException) {} + textForeTex.tryDispose() + textBackTex.tryDispose() - chrrom0.dispose() - chrrom.dispose() + chrrom0.tryDispose() + chrrom.tryDispose() unusedArea.destroy() scanlineOffsets.destroy() instArea.destroy() diff --git a/tsvm_core/src/net/torvald/tsvm/peripheral/IOSpace.kt b/tsvm_core/src/net/torvald/tsvm/peripheral/IOSpace.kt index 52450db..4a1b1b0 100644 --- a/tsvm_core/src/net/torvald/tsvm/peripheral/IOSpace.kt +++ b/tsvm_core/src/net/torvald/tsvm/peripheral/IOSpace.kt @@ -27,20 +27,20 @@ class IOSpace(val vm: VM) : PeriBase, InputProcessor { private val keyboardBuffer = CircularArray(32, true) internal val blockTransferRx = arrayOf( - UnsafeHelper.allocate(4096), - UnsafeHelper.allocate(4096), - UnsafeHelper.allocate(4096), - UnsafeHelper.allocate(4096) + UnsafeHelper.allocate(4096, this), + UnsafeHelper.allocate(4096, this), + UnsafeHelper.allocate(4096, this), + UnsafeHelper.allocate(4096, this) ) internal val blockTransferTx = arrayOf( - UnsafeHelper.allocate(4096), - UnsafeHelper.allocate(4096), - UnsafeHelper.allocate(4096), - UnsafeHelper.allocate(4096) + UnsafeHelper.allocate(4096, this), + UnsafeHelper.allocate(4096, this), + UnsafeHelper.allocate(4096, this), + UnsafeHelper.allocate(4096, this) ) /*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) diff --git a/tsvm_core/src/net/torvald/tsvm/peripheral/OpenALBufferedAudioDevice.kt b/tsvm_core/src/net/torvald/tsvm/peripheral/OpenALBufferedAudioDevice.kt index 63fa110..bbd9e4f 100644 --- a/tsvm_core/src/net/torvald/tsvm/peripheral/OpenALBufferedAudioDevice.kt +++ b/tsvm_core/src/net/torvald/tsvm/peripheral/OpenALBufferedAudioDevice.kt @@ -1,7 +1,6 @@ package net.torvald.tsvm.peripheral 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.math.MathUtils import com.badlogic.gdx.utils.GdxRuntimeException @@ -120,6 +119,14 @@ class OpenALBufferedAudioDevice( 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) { var offset = offset var length = length @@ -129,8 +136,11 @@ class OpenALBufferedAudioDevice( if (sourceID == -1) return if (buffers == null) { buffers = BufferUtils.createIntBuffer(bufferCount) + AL10.alGetError() 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.alSourcef(sourceID, AL10.AL_GAIN, volume) diff --git a/tsvm_core/src/net/torvald/tsvm/peripheral/RamBank.kt b/tsvm_core/src/net/torvald/tsvm/peripheral/RamBank.kt index 64c8afd..48ee478 100644 --- a/tsvm_core/src/net/torvald/tsvm/peripheral/RamBank.kt +++ b/tsvm_core/src/net/torvald/tsvm/peripheral/RamBank.kt @@ -19,7 +19,7 @@ open class RamBank(val vm: VM, bankCount: Int) : PeriBase { 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 map1 = 1 diff --git a/tsvm_core/src/net/torvald/tsvm/peripheral/TTY.kt b/tsvm_core/src/net/torvald/tsvm/peripheral/TTY.kt index dbc5efd..1b2f84c 100644 --- a/tsvm_core/src/net/torvald/tsvm/peripheral/TTY.kt +++ b/tsvm_core/src/net/torvald/tsvm/peripheral/TTY.kt @@ -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 textBuffer = UnsafeHelper.allocate(TEXT_ROWS * TEXT_COLS * 2L) + private val textBuffer = UnsafeHelper.allocate(TEXT_ROWS * TEXT_COLS * 2L, this) override var rawCursorPos = 0 private val TEXT_AREA_SIZE = TEXT_COLS * TEXT_ROWS diff --git a/tsvm_core/src/net/torvald/tsvm/vdc/Videotron2K.kt b/tsvm_core/src/net/torvald/tsvm/vdc/Videotron2K.kt index 5e5276f..4a42ad1 100644 --- a/tsvm_core/src/net/torvald/tsvm/vdc/Videotron2K.kt +++ b/tsvm_core/src/net/torvald/tsvm/vdc/Videotron2K.kt @@ -124,8 +124,8 @@ class Videotron2K(var gpu: GraphicsAdapter?) { """.trimIndent() } - internal var regs = UnsafeHelper.allocate(16 * 4) - internal var internalMem = UnsafeHelper.allocate(16384) + internal var regs = UnsafeHelper.allocate(16 * 4, this) +// internal var internalMem = UnsafeHelper.allocate(16384, this) internal var callStack = Stack>() // Pair of Scene-ID (has SCENE_PREFIX; 0 with no prefix for root scene) and ProgramCounter /* Compile-time variables */ diff --git a/tsvm_executable/src/net/torvald/tsvm/ProfilesMenu.kt b/tsvm_executable/src/net/torvald/tsvm/ProfilesMenu.kt index d485541..06c7f47 100644 --- a/tsvm_executable/src/net/torvald/tsvm/ProfilesMenu.kt +++ b/tsvm_executable/src/net/torvald/tsvm/ProfilesMenu.kt @@ -87,7 +87,7 @@ class ProfilesMenu(parent: VMEmuExecutable, x: Int, y: Int, w: Int, h: Int) : Em parent.killVMenv(theVM) } else if (mx in 395..415 && !theVM.isRunning) { - parent.initVMenv(theVM) + parent.initVMenv(theVM, profileName) } } } diff --git a/tsvm_executable/src/net/torvald/tsvm/VMEmuExecutable.kt b/tsvm_executable/src/net/torvald/tsvm/VMEmuExecutable.kt index 15d17da..7c05e8b 100644 --- a/tsvm_executable/src/net/torvald/tsvm/VMEmuExecutable.kt +++ b/tsvm_executable/src/net/torvald/tsvm/VMEmuExecutable.kt @@ -68,7 +68,7 @@ class VMEmuExecutable(val windowWidth: Int, val windowHeight: Int, var panelsX: val watchdogs = hashMapOf("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(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 camera: OrthographicCamera - var vmRunners = HashMap() // - var coroutineJobs = HashMap() // + var vmRunners = HashMap() // + var coroutineJobs = HashMap() // companion object { val APPDATADIR = TsvmEmulator.defaultDir @@ -100,7 +100,7 @@ class VMEmuExecutable(val windowWidth: Int, val windowHeight: Int, var panelsX: private val currentlyLoadedProfiles = HashMap() internal fun getVMbyProfileName(name: String): VM? { if (profiles.containsKey(name)) { - return currentlyLoadedProfiles.getOrPut(name) { _makeVMfromJson(profiles[name]!!, name) } + return currentlyLoadedProfiles.getOrPut(name) { makeVMfromJson(profiles[name]!!, name) } } else return null @@ -177,7 +177,7 @@ class VMEmuExecutable(val windowWidth: Int, val windowHeight: Int, var panelsX: vms[0] = VMRunnerInfo(vm, "Initial VM")*/ val vm1 = getVMbyProfileName("Initial VM")!! - initVMenv(vm1) + initVMenv(vm1, "Initial VM") vms[0] = VMRunnerInfo(vm1, "Initial VM") 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 } - internal fun initVMenv(vm: VM) { + internal fun initVMenv(vm: VM, profileName: String) { val gpu = ReferenceGraphicsAdapter2("./assets", vm) - VMSetupBroker.initVMenv(vm, gpu, vmRunners, coroutineJobs) { + VMSetupBroker.initVMenv(vm, profiles[profileName]!!, profileName, gpu, vmRunners, coroutineJobs) { it.printStackTrace() 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) } } - private fun reboot(vm: VM) { + private fun reboot(profileName: String) { + val vm = currentlyLoadedProfiles[profileName]!! + vmRunners[vm.id]!!.close() coroutineJobs[vm.id]!!.cancel("reboot requested") vm.init() - initVMenv(vm) + initVMenv(vm, profileName) } private fun updateGame(delta: Float) { @@ -279,7 +281,7 @@ class VMEmuExecutable(val windowWidth: Int, val windowHeight: Int, var panelsX: } 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) } @@ -302,7 +304,7 @@ class VMEmuExecutable(val windowWidth: Int, val windowHeight: Int, var panelsX: it.fillRect(xoff, 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.color = if (index == currentVMselection) EmulatorGuiToolkit.Theme.COL_ACTIVE else EmulatorGuiToolkit.Theme.COL_ACTIVE2 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() - /** * 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'") - + val assetsDir = json.getString("assetsdir") val ramsize = json.getLong("ramsize") 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) - // 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 }