mirror of
https://github.com/curioustorvald/tsvm.git
synced 2026-03-07 19:51:51 +09:00
improved way of initialising vms at (re)launch
This commit is contained in:
@@ -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)
|
||||||
|
|||||||
@@ -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};` +
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
6
tsvm_core/src/net/torvald/tsvm/Utils.kt
Normal file
6
tsvm_core/src/net/torvald/tsvm/Utils.kt
Normal 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("")
|
||||||
@@ -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() }
|
||||||
|
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 */
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,7 +513,7 @@ 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")
|
||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user