Refactor MTMCClock frame handling, enhance emulator performance, integrate immediateTimeout, and optimize rendering logic for RegisterView, MemoryView, and BufferedImage.

This commit is contained in:
2025-08-17 16:16:57 +02:00
parent 6acf781324
commit f169dce339
19 changed files with 5132 additions and 99 deletions

View File

@@ -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)
} }
} }

View File

@@ -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(

View File

@@ -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) }
} }

View File

@@ -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) {

View File

@@ -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)

View File

@@ -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
} }

View File

@@ -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("!") }

View File

@@ -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")

View File

@@ -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()
} }

View File

@@ -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

View 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()
}
}

View File

@@ -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)

View File

@@ -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)

View File

@@ -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()
}
}

View File

@@ -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}"
} }
} }
} }

View File

@@ -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 */

View File

@@ -0,0 +1,5 @@
package mtmc.emulator
actual fun createBufferedImage(width: Int, height: Int): BufferedImage {
TODO("Not yet implemented")
}

View File

@@ -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 {}