generated from rnentjes/kotlin-server-web-undertow
Refactor MTMCClock frame handling, enhance emulator performance, integrate immediateTimeout, and optimize rendering logic for RegisterView, MemoryView, and BufferedImage.
This commit is contained in:
@@ -34,4 +34,4 @@ class Data(labels: MutableList<MTMCToken>, lineNumber: Int) : ASMElement(labels,
|
|||||||
override fun addError(err: String) {
|
override fun addError(err: String) {
|
||||||
addError(labels.last(), err)
|
addError(labels.last(), err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,30 +1,13 @@
|
|||||||
package mtmc.emulator
|
package mtmc.emulator
|
||||||
|
|
||||||
class BufferedImage(
|
expect fun createBufferedImage(width: Int, height: Int): BufferedImage
|
||||||
val width: Int,
|
|
||||||
val height: Int,
|
|
||||||
val type: Int
|
|
||||||
) {
|
|
||||||
val display = ByteArray(width * height * 4)
|
|
||||||
|
|
||||||
fun getRGB(x: Int, y: Int): Int {
|
interface BufferedImage {
|
||||||
check(x in 0 until width && y in 0 until height)
|
val width: Int
|
||||||
|
val height: Int
|
||||||
|
|
||||||
val offset = (x + y * width) * 4
|
fun getRGB(x: Int, y: Int): Int
|
||||||
return display[offset + 0].toInt() +
|
fun setRGB(x: Int, y: Int, intVal: Int)
|
||||||
display[offset + 1].toInt() shl 8 +
|
|
||||||
display[offset + 2].toInt() shl 16
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setRGB(x: Int, y: Int, intVal: Int) {
|
|
||||||
check(x in 0 until width && y in 0 until height)
|
|
||||||
|
|
||||||
val offset = (x + y * width) * 4
|
|
||||||
display[offset + 0] = intVal.toByte()
|
|
||||||
display[offset + 1] = (intVal shr 8).toByte()
|
|
||||||
display[offset + 2] = (intVal shr 16).toByte()
|
|
||||||
display[offset + 3] = 255.toByte()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class Dimension(
|
class Dimension(
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import mtmc.emulator.MonTanaMiniComputer.ComputerStatus
|
|||||||
import mtmc.util.currentTimeMillis
|
import mtmc.util.currentTimeMillis
|
||||||
import mtmc.util.requestAnimationFrame
|
import mtmc.util.requestAnimationFrame
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
|
import kotlin.math.min
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@@ -18,10 +19,12 @@ class MTMCClock(
|
|||||||
var virtual: Long = 0
|
var virtual: Long = 0
|
||||||
|
|
||||||
var startTime = currentTimeMillis()
|
var startTime = currentTimeMillis()
|
||||||
|
var lastFrame = 0.0
|
||||||
|
|
||||||
var speed: Long = 0
|
var speed: Long = 0
|
||||||
|
|
||||||
var instructionsToRun = 0.0
|
var instructionsToRun = 0.0
|
||||||
|
var frame = 0
|
||||||
|
|
||||||
fun run() {
|
fun run() {
|
||||||
requestAnimationFrame { handleFrame(it) }
|
requestAnimationFrame { handleFrame(it) }
|
||||||
@@ -30,23 +33,37 @@ class MTMCClock(
|
|||||||
fun handleFrame(time: Double) {
|
fun handleFrame(time: Double) {
|
||||||
// figure out how many instructions to execute this 'time' duration
|
// figure out how many instructions to execute this 'time' duration
|
||||||
// maximize time so we don't hang if the emulator is too slow
|
// maximize time so we don't hang if the emulator is too slow
|
||||||
val actualTime = max(time, 0.16)
|
if (lastFrame == 0.0) {
|
||||||
|
lastFrame = time
|
||||||
|
requestAnimationFrame { handleFrame(it) }
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val delta = time - lastFrame
|
||||||
|
lastFrame = time
|
||||||
|
|
||||||
|
val actualTime = min(delta / 1000.0, 0.05)
|
||||||
|
|
||||||
// assume 1Hz = 1 instruction/second
|
// assume 1Hz = 1 instruction/second
|
||||||
if (computer.getStatus() == ComputerStatus.EXECUTING) {
|
if (computer.getStatus() == ComputerStatus.EXECUTING) {
|
||||||
speed = max(computer.speed, 0).toLong()
|
speed = max(computer.speed, 0).toLong()
|
||||||
if (speed == 0L) {
|
if (speed == 0L) {
|
||||||
speed = 1000000L
|
speed = 1L
|
||||||
}
|
}
|
||||||
|
|
||||||
instructionsToRun += time * speed
|
instructionsToRun += actualTime * speed
|
||||||
val pulse: Long = instructionsToRun.toLong()
|
val pulse: Long = instructionsToRun.toLong()
|
||||||
instructionsToRun -= pulse
|
instructionsToRun -= pulse
|
||||||
|
|
||||||
instructions += computer.pulse(pulse)
|
val time = currentTimeMillis()
|
||||||
|
val ir = computer.pulse(pulse)
|
||||||
|
instructions += ir
|
||||||
|
if (frame % 100 == 0) {
|
||||||
|
println("Instructions ran: $ir (delta = $delta, actualTime = $actualTime, speed = $speed, duration = ${currentTimeMillis() - time})")
|
||||||
|
}
|
||||||
|
|
||||||
virtual += instructions
|
virtual += instructions
|
||||||
ips = (instructions / time).toLong()
|
ips = (instructions / actualTime).toLong()
|
||||||
|
frame++
|
||||||
|
|
||||||
requestAnimationFrame { handleFrame(it) }
|
requestAnimationFrame { handleFrame(it) }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
package mtmc.emulator
|
package mtmc.emulator
|
||||||
|
|
||||||
import mtmc.os.fs.Console
|
import mtmc.os.fs.Console
|
||||||
import mtmc.os.fs.System
|
|
||||||
import mtmc.os.shell.Shell
|
import mtmc.os.shell.Shell
|
||||||
import mtmc.tokenizer.MTMCScanner
|
import mtmc.tokenizer.MTMCScanner
|
||||||
import mtmc.tokenizer.MTMCToken
|
import mtmc.tokenizer.MTMCToken
|
||||||
|
|
||||||
class MTMCConsole(private val computer: MonTanaMiniComputer) {
|
class MTMCConsole(private val computer: MonTanaMiniComputer) {
|
||||||
var mode: Mode = Mode.NON_INTERACTIVE
|
var mode: Mode = Mode.INTERACTIVE
|
||||||
var sysConsole: Console? = null
|
var sysConsole: Console? = null
|
||||||
|
|
||||||
// non-interactive data
|
// non-interactive data
|
||||||
@@ -19,12 +18,7 @@ class MTMCConsole(private val computer: MonTanaMiniComputer) {
|
|||||||
// TODO invert so shell is driving and console is just IO
|
// TODO invert so shell is driving and console is just IO
|
||||||
fun start() {
|
fun start() {
|
||||||
mode = Mode.INTERACTIVE
|
mode = Mode.INTERACTIVE
|
||||||
sysConsole = System.console
|
|
||||||
Shell.printShellWelcome(computer)
|
Shell.printShellWelcome(computer)
|
||||||
while (true) {
|
|
||||||
val cmd = sysConsole!!.readLine("mtmc > ")
|
|
||||||
computer.oS.processCommand(cmd)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun println(x: String) {
|
fun println(x: String) {
|
||||||
|
|||||||
@@ -3,11 +3,7 @@ package mtmc.emulator
|
|||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
|
|
||||||
class MTMCDisplay(private val computer: MonTanaMiniComputer) {
|
class MTMCDisplay(private val computer: MonTanaMiniComputer) {
|
||||||
private val buffer = BufferedImage(
|
private val buffer: BufferedImage? = null
|
||||||
COLS,
|
|
||||||
ROWS,
|
|
||||||
1
|
|
||||||
) // BufferedImage.TYPE_INT_ARGB
|
|
||||||
private var currentColor: DisplayColor? = null
|
private var currentColor: DisplayColor? = null
|
||||||
private var graphics: Array<BufferedImage> = arrayOf()
|
private var graphics: Array<BufferedImage> = arrayOf()
|
||||||
private var byteArray: ByteArray = ByteArray(0)
|
private var byteArray: ByteArray = ByteArray(0)
|
||||||
@@ -88,7 +84,7 @@ class MTMCDisplay(private val computer: MonTanaMiniComputer) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
graphics = Array(data.size) { index ->
|
graphics = Array(data.size) { index ->
|
||||||
loadImage(data[index]) ?: BufferedImage(160, 144, 1) //BufferedImage.TYPE_INT_RGB)
|
loadImage(data[index]) ?: createBufferedImage(160, 144) //BufferedImage.TYPE_INT_RGB)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,12 +107,12 @@ class MTMCDisplay(private val computer: MonTanaMiniComputer) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun setPixel(col: Int, row: Int, color: DisplayColor) {
|
fun setPixel(col: Int, row: Int, color: DisplayColor) {
|
||||||
buffer.setRGB(col, row, color.intVal)
|
buffer?.setRGB(col, row, color.intVal)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getPixel(col: Int, row: Int): Short {
|
fun getPixel(col: Int, row: Int): Short {
|
||||||
val rgb = buffer.getRGB(col, row)
|
val rgb = buffer?.getRGB(col, row) ?: 0
|
||||||
return DisplayColor.Companion.indexFromInt(rgb)
|
return DisplayColor.indexFromInt(rgb)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun drawLine(startCol: Short, startRow: Short, endCol: Short, endRow: Short) {
|
fun drawLine(startCol: Short, startRow: Short, endCol: Short, endRow: Short) {
|
||||||
@@ -231,7 +227,7 @@ class MTMCDisplay(private val computer: MonTanaMiniComputer) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun scaleImage(original: BufferedImage, scaleDimensions: Dimension): BufferedImage {
|
fun scaleImage(original: BufferedImage, scaleDimensions: Dimension): BufferedImage {
|
||||||
val resized = BufferedImage(scaleDimensions.width, scaleDimensions.height, original.type)
|
val resized = createBufferedImage(scaleDimensions.width, scaleDimensions.height)
|
||||||
/*
|
/*
|
||||||
val g = resized.createGraphics()
|
val g = resized.createGraphics()
|
||||||
g.setRenderingHint(
|
g.setRenderingHint(
|
||||||
@@ -248,8 +244,7 @@ class MTMCDisplay(private val computer: MonTanaMiniComputer) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun convertImage(image: BufferedImage): BufferedImage {
|
fun convertImage(image: BufferedImage): BufferedImage {
|
||||||
val arbg =
|
val arbg = createBufferedImage(image.width, image.height) //BufferedImage.TYPE_4BYTE_ABGR)
|
||||||
BufferedImage(image.width, image.height, 2) //BufferedImage.TYPE_4BYTE_ABGR)
|
|
||||||
for (x in 0..<image.width) {
|
for (x in 0..<image.width) {
|
||||||
for (y in 0..<image.height) {
|
for (y in 0..<image.height) {
|
||||||
val rgb = image.getRGB(x, y)
|
val rgb = image.getRGB(x, y)
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ class MonTanaMiniComputer {
|
|||||||
var memory: ByteArray = ByteArray(MEMORY_SIZE)
|
var memory: ByteArray = ByteArray(MEMORY_SIZE)
|
||||||
var breakpoints: ByteArray = ByteArray(MEMORY_SIZE)
|
var breakpoints: ByteArray = ByteArray(MEMORY_SIZE)
|
||||||
private var status: ComputerStatus? = ComputerStatus.READY
|
private var status: ComputerStatus? = ComputerStatus.READY
|
||||||
var speed: Int = 1000000
|
var speed: Int = 1
|
||||||
set(speed) {
|
set(speed) {
|
||||||
field = speed
|
field = speed
|
||||||
this.notifyOfExecutionUpdate()
|
this.notifyOfExecutionUpdate()
|
||||||
@@ -57,11 +57,12 @@ class MonTanaMiniComputer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun load(code: ByteArray, data: ByteArray, debugInfo: DebugInfo?) {
|
fun load(
|
||||||
load(code, data, Array(0) { ByteArray(0) }, debugInfo)
|
code: ByteArray,
|
||||||
}
|
data: ByteArray,
|
||||||
|
graphics: Array<ByteArray> = arrayOf(),
|
||||||
fun load(code: ByteArray, data: ByteArray, graphics: Array<ByteArray>, debugInfo: DebugInfo?) {
|
debugInfo: DebugInfo? = null
|
||||||
|
) {
|
||||||
this.debugInfo = debugInfo
|
this.debugInfo = debugInfo
|
||||||
|
|
||||||
// reset memory
|
// reset memory
|
||||||
@@ -72,7 +73,7 @@ class MonTanaMiniComputer {
|
|||||||
setRegisterValue(Register.CB, codeBoundary - 1)
|
setRegisterValue(Register.CB, codeBoundary - 1)
|
||||||
|
|
||||||
val dataBoundary = codeBoundary + data.size
|
val dataBoundary = codeBoundary + data.size
|
||||||
code.copyInto(memory, codeBoundary, 0, data.size)
|
data.copyInto(memory, codeBoundary, 0, data.size)
|
||||||
setRegisterValue(Register.DB, dataBoundary - 1)
|
setRegisterValue(Register.DB, dataBoundary - 1)
|
||||||
|
|
||||||
|
|
||||||
@@ -726,7 +727,11 @@ class MonTanaMiniComputer {
|
|||||||
private fun badInstruction(instruction: Short) {
|
private fun badInstruction(instruction: Short) {
|
||||||
setStatus(ComputerStatus.PERMANENT_ERROR)
|
setStatus(ComputerStatus.PERMANENT_ERROR)
|
||||||
// TODO implement flags
|
// TODO implement flags
|
||||||
console.println("BAD INSTRUCTION: 0x" + (instruction.toInt() and 0xFFFF).toHexString())
|
console.println(
|
||||||
|
"BAD INSTRUCTION: 0x" + (instruction.toHexString()) + " at PC " + getRegisterValue(
|
||||||
|
Register.PC
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun fetchCurrentInstruction() {
|
fun fetchCurrentInstruction() {
|
||||||
@@ -748,7 +753,7 @@ class MonTanaMiniComputer {
|
|||||||
val upperByte = fetchByteFromMemory(address).toShort()
|
val upperByte = fetchByteFromMemory(address).toShort()
|
||||||
val lowerByte = fetchByteFromMemory(address + 1)
|
val lowerByte = fetchByteFromMemory(address + 1)
|
||||||
var value = (upperByte.toInt() shl 8).toShort()
|
var value = (upperByte.toInt() shl 8).toShort()
|
||||||
val i = value.toInt() or lowerByte.toInt()
|
val i = value.toInt() + (lowerByte.toInt() and 255)
|
||||||
value = i.toShort()
|
value = i.toShort()
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -240,7 +240,38 @@ class MTOS(private val computer: MonTanaMiniComputer) {
|
|||||||
try {
|
try {
|
||||||
// special handling for game-of-life cell files
|
// special handling for game-of-life cell files
|
||||||
if ("cells" == fileType) {
|
if ("cells" == fileType) {
|
||||||
val str = Files.readString(file.toPath())
|
//val str = Files.readString(file.toPath())
|
||||||
|
val str = """
|
||||||
|
! galaxy
|
||||||
|
! Kok's galaxy - a whirling oscillator
|
||||||
|
.......................................
|
||||||
|
.......................................
|
||||||
|
.......................................
|
||||||
|
.......................................
|
||||||
|
.......................................
|
||||||
|
.......................................
|
||||||
|
.......................................
|
||||||
|
.......................................
|
||||||
|
.......................................
|
||||||
|
.......................................
|
||||||
|
.......................................
|
||||||
|
.......................................
|
||||||
|
.......................................
|
||||||
|
...............OOOOOO.OO.....
|
||||||
|
...............OOOOOO.OO
|
||||||
|
......................OO
|
||||||
|
...............OO.....OO
|
||||||
|
...............OO.....OO
|
||||||
|
...............OO.....OO
|
||||||
|
...............OO.......
|
||||||
|
...............OO.OOOOOO
|
||||||
|
...............OO.OOOOOO.......................................
|
||||||
|
.......................................
|
||||||
|
.......................................
|
||||||
|
.......................................
|
||||||
|
.......................................
|
||||||
|
.......................................
|
||||||
|
""".trimIndent()
|
||||||
val lines: List<String> =
|
val lines: List<String> =
|
||||||
str.split("\n".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
|
str.split("\n".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
|
||||||
.filter { s: String? -> !s!!.startsWith("!") }
|
.filter { s: String? -> !s!!.startsWith("!") }
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ class File(
|
|||||||
val name: String
|
val name: String
|
||||||
) {
|
) {
|
||||||
|
|
||||||
var directory: Boolean = TODO("initialize me")
|
var directory: Boolean = false
|
||||||
|
|
||||||
constructor(name: String) : this(null, name)
|
constructor(name: String) : this(null, name)
|
||||||
|
|
||||||
@@ -26,9 +26,7 @@ class File(
|
|||||||
"${parent.getPath()}/$name"
|
"${parent.getPath()}/$name"
|
||||||
}
|
}
|
||||||
|
|
||||||
fun exists(): Boolean {
|
fun exists(): Boolean = true
|
||||||
TODO("Not yet implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getAbsolutePath(): String {
|
fun getAbsolutePath(): String {
|
||||||
TODO("Not yet implemented")
|
TODO("Not yet implemented")
|
||||||
|
|||||||
@@ -11,13 +11,9 @@ object BinaryUtils {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
val returnValue = (value.toInt() and 0xffff) ushr (start - totalBits)
|
val returnValue = (value.toInt() and 0xffff) ushr (start - totalBits)
|
||||||
var mask = 0
|
var mask = 1
|
||||||
var toShift = totalBits
|
mask = mask shl totalBits
|
||||||
while (toShift > 0) {
|
mask--
|
||||||
toShift--
|
|
||||||
mask = mask shl 1
|
|
||||||
mask = mask + 1
|
|
||||||
}
|
|
||||||
return (returnValue and mask).toShort()
|
return (returnValue and mask).toShort()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,3 +4,4 @@ expect fun currentTimeMillis(): Long
|
|||||||
|
|
||||||
expect fun requestAnimationFrame(action: (Double) -> Unit)
|
expect fun requestAnimationFrame(action: (Double) -> Unit)
|
||||||
|
|
||||||
|
expect fun immediateTimeout(action: (Double) -> Unit): Int
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
34
src/jsMain/kotlin/mtmc/emulator/BufferedImage.js.kt
Normal file
34
src/jsMain/kotlin/mtmc/emulator/BufferedImage.js.kt
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
package mtmc.emulator
|
||||||
|
|
||||||
|
actual fun createBufferedImage(
|
||||||
|
width: Int,
|
||||||
|
height: Int
|
||||||
|
): BufferedImage = BufferedImageImpl(width, height, 1)
|
||||||
|
|
||||||
|
class BufferedImageImpl(
|
||||||
|
override val width: Int,
|
||||||
|
override val height: Int,
|
||||||
|
val type: Int
|
||||||
|
) : BufferedImage {
|
||||||
|
val display = ByteArray(width * height * 4)
|
||||||
|
|
||||||
|
override fun getRGB(x: Int, y: Int): Int {
|
||||||
|
check(x in 0 until width && y in 0 until height)
|
||||||
|
|
||||||
|
val offset = (x + y * width) * 4
|
||||||
|
return display[offset + 0].toInt() +
|
||||||
|
display[offset + 1].toInt() shl 8 +
|
||||||
|
display[offset + 2].toInt() shl 16
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setRGB(x: Int, y: Int, intVal: Int) {
|
||||||
|
check(x in 0 until width && y in 0 until height)
|
||||||
|
|
||||||
|
val offset = (x + y * width) * 4
|
||||||
|
display[offset + 0] = intVal.toByte()
|
||||||
|
display[offset + 1] = (intVal shr 8).toByte()
|
||||||
|
display[offset + 2] = (intVal shr 16).toByte()
|
||||||
|
display[offset + 3] = 255.toByte()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,9 +1,22 @@
|
|||||||
package mtmc.util
|
package mtmc.util
|
||||||
|
|
||||||
import kotlinx.browser.window
|
import kotlinx.browser.window
|
||||||
|
import mtmc.mainView
|
||||||
import kotlin.js.Date
|
import kotlin.js.Date
|
||||||
|
|
||||||
|
var lastMemoryUpdate = currentTimeMillis()
|
||||||
|
|
||||||
actual fun currentTimeMillis(): Long = Date().getTime().toLong()
|
actual fun currentTimeMillis(): Long = Date().getTime().toLong()
|
||||||
actual fun requestAnimationFrame(action: (Double) -> Unit) {
|
actual fun requestAnimationFrame(action: (Double) -> Unit) {
|
||||||
window.requestAnimationFrame(action)
|
window.requestAnimationFrame {
|
||||||
|
action(it)
|
||||||
|
|
||||||
|
mainView.registerView.requestUpdate()
|
||||||
|
if (currentTimeMillis() - lastMemoryUpdate > 100) {
|
||||||
|
mainView.memoryView.requestUpdate()
|
||||||
|
lastMemoryUpdate = currentTimeMillis()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
actual fun immediateTimeout(action: (Double) -> Unit): Int = window.setTimeout(action, 0)
|
||||||
|
|||||||
@@ -24,16 +24,12 @@ class ConsoleView(
|
|||||||
|
|
||||||
override fun HtmlBuilder.render() {
|
override fun HtmlBuilder.render() {
|
||||||
div("console") {
|
div("console") {
|
||||||
+"Console view"
|
|
||||||
|
|
||||||
onClickFunction = {
|
onClickFunction = {
|
||||||
inputElement?.focus()
|
inputElement?.focus()
|
||||||
}
|
}
|
||||||
div("console-history") {
|
div("console-history") {
|
||||||
for (line in history) {
|
div {
|
||||||
div {
|
+computer.console.getOutput()
|
||||||
+line
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
div("console-input") {
|
div("console-input") {
|
||||||
@@ -64,7 +60,7 @@ class ConsoleView(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun handleCommand() {
|
private fun handleCommand() {
|
||||||
history.add(input)
|
//history.add(input)
|
||||||
|
|
||||||
Shell.execCommand(input, computer)
|
Shell.execCommand(input, computer)
|
||||||
|
|
||||||
|
|||||||
@@ -1,21 +1,51 @@
|
|||||||
package mtmc.view
|
package mtmc.view
|
||||||
|
|
||||||
|
import kotlinx.html.classes
|
||||||
import kotlinx.html.div
|
import kotlinx.html.div
|
||||||
import kotlinx.html.table
|
import kotlinx.html.table
|
||||||
import kotlinx.html.td
|
import kotlinx.html.td
|
||||||
|
import kotlinx.html.title
|
||||||
import kotlinx.html.tr
|
import kotlinx.html.tr
|
||||||
|
import mtmc.asm.instructions.Instruction
|
||||||
import mtmc.emulator.MonTanaMiniComputer
|
import mtmc.emulator.MonTanaMiniComputer
|
||||||
|
import mtmc.emulator.Register
|
||||||
import nl.astraeus.komp.HtmlBuilder
|
import nl.astraeus.komp.HtmlBuilder
|
||||||
import nl.astraeus.komp.Komponent
|
import nl.astraeus.komp.Komponent
|
||||||
|
|
||||||
fun ByteArray.asHex(address: Int): String {
|
fun ByteArray.asHex(address: Int): String {
|
||||||
val value = this[address].toInt() + this[address + 1].toInt() * 256
|
val value = getShort(address)
|
||||||
return value.toShort().toHexString()
|
return value.toHexString()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun ByteArray.getShort(address: Int): Short {
|
||||||
|
val value = (this[address].toInt() and 0xff) * 256 + (this[address + 1].toInt() and 0xff)
|
||||||
|
return value.toShort()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
enum class DisplayFormat {
|
||||||
|
DYN,
|
||||||
|
HEX,
|
||||||
|
DEC,
|
||||||
|
INS,
|
||||||
|
STR
|
||||||
|
}
|
||||||
|
|
||||||
|
private val ASCII_CODES = arrayOf<String>(
|
||||||
|
"NUL", "SOH", "STX", "ETX",
|
||||||
|
"EOT", "ENT", "ACK", "BEL",
|
||||||
|
"BS", "\\t", "\\n", "VT",
|
||||||
|
"\\f", "\\r", "SO", "SI",
|
||||||
|
"DLE", "DC1", "DC2", "DC3",
|
||||||
|
"DC4", "NAK", "SYN", "ETB",
|
||||||
|
"CAN", "EM", "SUB", "ESC",
|
||||||
|
"FS", "GS", "RS", "US",
|
||||||
|
)
|
||||||
|
|
||||||
class MemoryView(
|
class MemoryView(
|
||||||
val computer: MonTanaMiniComputer
|
val computer: MonTanaMiniComputer
|
||||||
) : Komponent() {
|
) : Komponent() {
|
||||||
|
var displayFormat: DisplayFormat = DisplayFormat.DYN
|
||||||
|
|
||||||
override fun HtmlBuilder.render() {
|
override fun HtmlBuilder.render() {
|
||||||
div("memory-panel") {
|
div("memory-panel") {
|
||||||
@@ -24,12 +54,42 @@ class MemoryView(
|
|||||||
}
|
}
|
||||||
div("memory") {
|
div("memory") {
|
||||||
table {
|
table {
|
||||||
for (address in 0..<computer.memory.size step 16) {
|
for (baseAddress in 0..<computer.memory.size step 16) {
|
||||||
tr {
|
tr {
|
||||||
|
var previousInstruction = 0x0fff.toShort()
|
||||||
for (offset in 0..<8) {
|
for (offset in 0..<8) {
|
||||||
|
val address = baseAddress + offset * 2
|
||||||
|
val memoryClass = classFor(address)
|
||||||
|
val df = computeMemoryFormat(memoryClass)
|
||||||
|
val value = computer.memory.getShort(address)
|
||||||
|
|
||||||
td {
|
td {
|
||||||
+computer.memory.asHex(address + offset)
|
when (memoryClass) {
|
||||||
|
"curr" -> classes += "current-address"
|
||||||
|
"code" -> classes += "code"
|
||||||
|
"data" -> classes += "data"
|
||||||
|
"heap" -> classes += "heap"
|
||||||
|
"sta" -> classes += "stack"
|
||||||
|
}
|
||||||
|
|
||||||
|
title = address.toString() + " - " + value.toHexString()
|
||||||
|
|
||||||
|
when (df) {
|
||||||
|
DisplayFormat.DEC -> +value.toInt().toString()
|
||||||
|
DisplayFormat.INS -> {
|
||||||
|
+Instruction.disassemble(
|
||||||
|
value,
|
||||||
|
previousInstruction
|
||||||
|
)
|
||||||
|
previousInstruction = value
|
||||||
|
}
|
||||||
|
|
||||||
|
DisplayFormat.HEX -> +value.toHexString()
|
||||||
|
DisplayFormat.STR -> +get1252String(value)
|
||||||
|
else -> +"?"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -37,4 +97,52 @@ class MemoryView(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
fun classFor(address: Int): String = if (address >= computer.getRegisterValue(Register.SP)) {
|
||||||
|
"sta"
|
||||||
|
} else if (address == computer.getRegisterValue(Register.PC).toInt()) {
|
||||||
|
"curr"
|
||||||
|
} else if (address <= computer.getRegisterValue(Register.CB)) {
|
||||||
|
"code"
|
||||||
|
} else if (address <= computer.getRegisterValue(Register.DB)) {
|
||||||
|
"data"
|
||||||
|
} else if (address <= computer.getRegisterValue(Register.BP)) {
|
||||||
|
"heap"
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun computeMemoryFormat(memoryClass: String?): DisplayFormat {
|
||||||
|
return if (displayFormat == DisplayFormat.DYN) {
|
||||||
|
when (memoryClass) {
|
||||||
|
"sta" -> DisplayFormat.DEC
|
||||||
|
"code", "curr" -> DisplayFormat.INS
|
||||||
|
"data", "heap" -> DisplayFormat.STR
|
||||||
|
else -> DisplayFormat.HEX
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
displayFormat
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun get1252String(value: Short): String {
|
||||||
|
val topByte = (value.toInt() ushr 8).toByte()
|
||||||
|
val bottomByte = value.toByte()
|
||||||
|
|
||||||
|
return if (topByte.toInt() != 0) {
|
||||||
|
get1252String(topByte) + " " + get1252String(
|
||||||
|
bottomByte
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
get1252String(bottomByte)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun get1252String(value: Byte): String = if (value in 0..<32) {
|
||||||
|
ASCII_CODES[value.toInt()]
|
||||||
|
} else {
|
||||||
|
"" + (value.toInt() and 0xff).toChar()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,9 +1,13 @@
|
|||||||
package mtmc.view
|
package mtmc.view
|
||||||
|
|
||||||
|
import kotlinx.browser.document
|
||||||
|
import kotlinx.dom.addClass
|
||||||
|
import kotlinx.dom.removeClass
|
||||||
import kotlinx.html.TABLE
|
import kotlinx.html.TABLE
|
||||||
import kotlinx.html.classes
|
import kotlinx.html.classes
|
||||||
import kotlinx.html.div
|
import kotlinx.html.div
|
||||||
import kotlinx.html.hr
|
import kotlinx.html.hr
|
||||||
|
import kotlinx.html.id
|
||||||
import kotlinx.html.table
|
import kotlinx.html.table
|
||||||
import kotlinx.html.td
|
import kotlinx.html.td
|
||||||
import kotlinx.html.tr
|
import kotlinx.html.tr
|
||||||
@@ -11,6 +15,7 @@ import mtmc.emulator.MonTanaMiniComputer
|
|||||||
import mtmc.emulator.Register
|
import mtmc.emulator.Register
|
||||||
import nl.astraeus.komp.HtmlBuilder
|
import nl.astraeus.komp.HtmlBuilder
|
||||||
import nl.astraeus.komp.Komponent
|
import nl.astraeus.komp.Komponent
|
||||||
|
import org.w3c.dom.HTMLElement
|
||||||
|
|
||||||
class RegisterView(
|
class RegisterView(
|
||||||
val computer: MonTanaMiniComputer
|
val computer: MonTanaMiniComputer
|
||||||
@@ -40,6 +45,22 @@ class RegisterView(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun renderUpdate() {
|
||||||
|
for (register in 0..<18) {
|
||||||
|
val value = computer.registerFile[register]
|
||||||
|
for (bit in 15 downTo 0) {
|
||||||
|
val element = document.getElementById("bit-$bit-$register") as? HTMLElement ?: continue
|
||||||
|
if (value.toInt() and (1 shl bit) == 0) {
|
||||||
|
element.addClass("off")
|
||||||
|
} else {
|
||||||
|
element.removeClass("off")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val element = document.getElementById("register-$register") as? HTMLElement ?: continue
|
||||||
|
element.innerText = "${value.toInt() and 0xffff}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun TABLE.showRegister(register: Int) {
|
private fun TABLE.showRegister(register: Int) {
|
||||||
val name = Register.fromInteger(register)
|
val name = Register.fromInteger(register)
|
||||||
val value = computer.registerFile[register]
|
val value = computer.registerFile[register]
|
||||||
@@ -50,6 +71,7 @@ class RegisterView(
|
|||||||
td("register-lights") {
|
td("register-lights") {
|
||||||
for (bit in 15 downTo 0) {
|
for (bit in 15 downTo 0) {
|
||||||
div("blinken") {
|
div("blinken") {
|
||||||
|
id = "bit-$bit-$register"
|
||||||
if (value.toInt() and (1 shl bit) == 0) {
|
if (value.toInt() and (1 shl bit) == 0) {
|
||||||
classes += "off"
|
classes += "off"
|
||||||
}
|
}
|
||||||
@@ -60,7 +82,8 @@ class RegisterView(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
td("align-right") {
|
td("align-right") {
|
||||||
+"$value"
|
id = "register-$register"
|
||||||
|
+"${value.toInt() and 0xffff}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,8 +63,9 @@ table.register-table {
|
|||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
table.register-table tr td .align-right {
|
table.register-table tr td.align-right {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
|
min-width: 100px;
|
||||||
}
|
}
|
||||||
|
|
||||||
table.register-table tr td.register-lights {
|
table.register-table tr td.register-lights {
|
||||||
@@ -106,10 +107,34 @@ table.register-table tr td.register-lights {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.memory {
|
.memory {
|
||||||
font-family: monospace;
|
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.memory table tr td {
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.current-address {
|
||||||
|
background-color: #78e878;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code {
|
||||||
|
background-color: darkseagreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data {
|
||||||
|
background-color: lightgoldenrodyellow;
|
||||||
|
}
|
||||||
|
|
||||||
|
.heap {
|
||||||
|
background-color: lightcoral;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stack {
|
||||||
|
background-color: lightsalmon;
|
||||||
|
}
|
||||||
|
|
||||||
/* display */
|
/* display */
|
||||||
|
|
||||||
.display {
|
.display {
|
||||||
@@ -117,6 +142,7 @@ table.register-table tr td.register-lights {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.display-canvas {
|
.display-canvas {
|
||||||
|
padding: 24px 64px;
|
||||||
width: 320px;
|
width: 320px;
|
||||||
height: 288px;
|
height: 288px;
|
||||||
image-rendering: pixelated; /* Keeps sharp pixels, no smoothing */
|
image-rendering: pixelated; /* Keeps sharp pixels, no smoothing */
|
||||||
|
|||||||
5
src/jvmMain/kotlin/mtmc/emulator/BufferedImage.jvm.kt
Normal file
5
src/jvmMain/kotlin/mtmc/emulator/BufferedImage.jvm.kt
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
package mtmc.emulator
|
||||||
|
|
||||||
|
actual fun createBufferedImage(width: Int, height: Int): BufferedImage {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
@@ -4,3 +4,5 @@ actual fun currentTimeMillis(): Long = System.currentTimeMillis()
|
|||||||
actual fun requestAnimationFrame(action: (Double) -> Unit) {
|
actual fun requestAnimationFrame(action: (Double) -> Unit) {
|
||||||
error("requestAnimationFrame is not supported on JVM")
|
error("requestAnimationFrame is not supported on JVM")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
actual fun immediateTimeout(action: (Double) -> Unit): Int {}
|
||||||
Reference in New Issue
Block a user