From f169dce339e83b2f5499b1787d783b88d9da454c Mon Sep 17 00:00:00 2001 From: rnentjes Date: Sun, 17 Aug 2025 16:16:57 +0200 Subject: [PATCH] Refactor `MTMCClock` frame handling, enhance emulator performance, integrate `immediateTimeout`, and optimize rendering logic for `RegisterView`, `MemoryView`, and `BufferedImage`. --- src/commonMain/kotlin/mtmc/asm/data/Data.kt | 2 +- .../kotlin/mtmc/emulator/BufferedImage.kt | 29 +- .../kotlin/mtmc/emulator/MTMCClock.kt | 27 +- .../kotlin/mtmc/emulator/MTMCConsole.kt | 8 +- .../kotlin/mtmc/emulator/MTMCDisplay.kt | 19 +- .../mtmc/emulator/MonTanaMiniComputer.kt | 23 +- src/commonMain/kotlin/mtmc/os/MTOS.kt | 33 +- src/commonMain/kotlin/mtmc/os/fs/File.kt | 6 +- .../kotlin/mtmc/util/BinaryUtils.kt | 10 +- .../kotlin/mtmc/util/PlatformSpecific.kt | 1 + src/jsMain/kotlin/mtmc/Main.kt | 4834 ++++++++++++++++- .../kotlin/mtmc/emulator/BufferedImage.js.kt | 34 + .../kotlin/mtmc/util/PlatformSpecific.js.kt | 15 +- src/jsMain/kotlin/mtmc/view/ConsoleView.kt | 10 +- src/jsMain/kotlin/mtmc/view/MemoryView.kt | 118 +- src/jsMain/kotlin/mtmc/view/RegisterView.kt | 25 +- src/jsMain/resources/mtmc.css | 30 +- .../kotlin/mtmc/emulator/BufferedImage.jvm.kt | 5 + .../kotlin/mtmc/util/PlatformSpecific.jvm.kt | 2 + 19 files changed, 5132 insertions(+), 99 deletions(-) create mode 100644 src/jsMain/kotlin/mtmc/emulator/BufferedImage.js.kt create mode 100644 src/jvmMain/kotlin/mtmc/emulator/BufferedImage.jvm.kt diff --git a/src/commonMain/kotlin/mtmc/asm/data/Data.kt b/src/commonMain/kotlin/mtmc/asm/data/Data.kt index 70471d9..6f99443 100644 --- a/src/commonMain/kotlin/mtmc/asm/data/Data.kt +++ b/src/commonMain/kotlin/mtmc/asm/data/Data.kt @@ -34,4 +34,4 @@ class Data(labels: MutableList, lineNumber: Int) : ASMElement(labels, override fun addError(err: String) { addError(labels.last(), err) } -} \ No newline at end of file +} diff --git a/src/commonMain/kotlin/mtmc/emulator/BufferedImage.kt b/src/commonMain/kotlin/mtmc/emulator/BufferedImage.kt index 05d2496..b61ed27 100644 --- a/src/commonMain/kotlin/mtmc/emulator/BufferedImage.kt +++ b/src/commonMain/kotlin/mtmc/emulator/BufferedImage.kt @@ -1,30 +1,13 @@ package mtmc.emulator -class BufferedImage( - val width: Int, - val height: Int, - val type: Int -) { - val display = ByteArray(width * height * 4) +expect fun createBufferedImage(width: Int, height: Int): BufferedImage - fun getRGB(x: Int, y: Int): Int { - check(x in 0 until width && y in 0 until height) +interface BufferedImage { + val width: Int + val height: Int - val offset = (x + y * width) * 4 - return display[offset + 0].toInt() + - 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() - } + fun getRGB(x: Int, y: Int): Int + fun setRGB(x: Int, y: Int, intVal: Int) } class Dimension( diff --git a/src/commonMain/kotlin/mtmc/emulator/MTMCClock.kt b/src/commonMain/kotlin/mtmc/emulator/MTMCClock.kt index 8c43118..7b6c005 100644 --- a/src/commonMain/kotlin/mtmc/emulator/MTMCClock.kt +++ b/src/commonMain/kotlin/mtmc/emulator/MTMCClock.kt @@ -4,6 +4,7 @@ import mtmc.emulator.MonTanaMiniComputer.ComputerStatus import mtmc.util.currentTimeMillis import mtmc.util.requestAnimationFrame import kotlin.math.max +import kotlin.math.min /** * @@ -18,10 +19,12 @@ class MTMCClock( var virtual: Long = 0 var startTime = currentTimeMillis() + var lastFrame = 0.0 var speed: Long = 0 var instructionsToRun = 0.0 + var frame = 0 fun run() { requestAnimationFrame { handleFrame(it) } @@ -30,23 +33,37 @@ class MTMCClock( fun handleFrame(time: Double) { // figure out how many instructions to execute this 'time' duration // 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 if (computer.getStatus() == ComputerStatus.EXECUTING) { speed = max(computer.speed, 0).toLong() if (speed == 0L) { - speed = 1000000L + speed = 1L } - instructionsToRun += time * speed + instructionsToRun += actualTime * speed val pulse: Long = instructionsToRun.toLong() 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 - ips = (instructions / time).toLong() + ips = (instructions / actualTime).toLong() + frame++ requestAnimationFrame { handleFrame(it) } } diff --git a/src/commonMain/kotlin/mtmc/emulator/MTMCConsole.kt b/src/commonMain/kotlin/mtmc/emulator/MTMCConsole.kt index 64cba70..24374eb 100644 --- a/src/commonMain/kotlin/mtmc/emulator/MTMCConsole.kt +++ b/src/commonMain/kotlin/mtmc/emulator/MTMCConsole.kt @@ -1,13 +1,12 @@ package mtmc.emulator import mtmc.os.fs.Console -import mtmc.os.fs.System import mtmc.os.shell.Shell import mtmc.tokenizer.MTMCScanner import mtmc.tokenizer.MTMCToken class MTMCConsole(private val computer: MonTanaMiniComputer) { - var mode: Mode = Mode.NON_INTERACTIVE + var mode: Mode = Mode.INTERACTIVE var sysConsole: Console? = null // non-interactive data @@ -19,12 +18,7 @@ class MTMCConsole(private val computer: MonTanaMiniComputer) { // TODO invert so shell is driving and console is just IO fun start() { mode = Mode.INTERACTIVE - sysConsole = System.console Shell.printShellWelcome(computer) - while (true) { - val cmd = sysConsole!!.readLine("mtmc > ") - computer.oS.processCommand(cmd) - } } fun println(x: String) { diff --git a/src/commonMain/kotlin/mtmc/emulator/MTMCDisplay.kt b/src/commonMain/kotlin/mtmc/emulator/MTMCDisplay.kt index 5cf41cc..e50107b 100644 --- a/src/commonMain/kotlin/mtmc/emulator/MTMCDisplay.kt +++ b/src/commonMain/kotlin/mtmc/emulator/MTMCDisplay.kt @@ -3,11 +3,7 @@ package mtmc.emulator import kotlin.math.min class MTMCDisplay(private val computer: MonTanaMiniComputer) { - private val buffer = BufferedImage( - COLS, - ROWS, - 1 - ) // BufferedImage.TYPE_INT_ARGB + private val buffer: BufferedImage? = null private var currentColor: DisplayColor? = null private var graphics: Array = arrayOf() private var byteArray: ByteArray = ByteArray(0) @@ -88,7 +84,7 @@ class MTMCDisplay(private val computer: MonTanaMiniComputer) { } 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) { - buffer.setRGB(col, row, color.intVal) + buffer?.setRGB(col, row, color.intVal) } fun getPixel(col: Int, row: Int): Short { - val rgb = buffer.getRGB(col, row) - return DisplayColor.Companion.indexFromInt(rgb) + val rgb = buffer?.getRGB(col, row) ?: 0 + return DisplayColor.indexFromInt(rgb) } 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 { - val resized = BufferedImage(scaleDimensions.width, scaleDimensions.height, original.type) + val resized = createBufferedImage(scaleDimensions.width, scaleDimensions.height) /* val g = resized.createGraphics() g.setRenderingHint( @@ -248,8 +244,7 @@ class MTMCDisplay(private val computer: MonTanaMiniComputer) { } fun convertImage(image: BufferedImage): BufferedImage { - val arbg = - BufferedImage(image.width, image.height, 2) //BufferedImage.TYPE_4BYTE_ABGR) + val arbg = createBufferedImage(image.width, image.height) //BufferedImage.TYPE_4BYTE_ABGR) for (x in 0.., debugInfo: DebugInfo?) { + fun load( + code: ByteArray, + data: ByteArray, + graphics: Array = arrayOf(), + debugInfo: DebugInfo? = null + ) { this.debugInfo = debugInfo // reset memory @@ -72,7 +73,7 @@ class MonTanaMiniComputer { setRegisterValue(Register.CB, codeBoundary - 1) val dataBoundary = codeBoundary + data.size - code.copyInto(memory, codeBoundary, 0, data.size) + data.copyInto(memory, codeBoundary, 0, data.size) setRegisterValue(Register.DB, dataBoundary - 1) @@ -726,7 +727,11 @@ class MonTanaMiniComputer { private fun badInstruction(instruction: Short) { setStatus(ComputerStatus.PERMANENT_ERROR) // 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() { @@ -748,7 +753,7 @@ class MonTanaMiniComputer { val upperByte = fetchByteFromMemory(address).toShort() val lowerByte = fetchByteFromMemory(address + 1) 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() return value } diff --git a/src/commonMain/kotlin/mtmc/os/MTOS.kt b/src/commonMain/kotlin/mtmc/os/MTOS.kt index d9cb660..29ba098 100644 --- a/src/commonMain/kotlin/mtmc/os/MTOS.kt +++ b/src/commonMain/kotlin/mtmc/os/MTOS.kt @@ -240,7 +240,38 @@ class MTOS(private val computer: MonTanaMiniComputer) { try { // special handling for game-of-life cell files 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 = str.split("\n".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() .filter { s: String? -> !s!!.startsWith("!") } diff --git a/src/commonMain/kotlin/mtmc/os/fs/File.kt b/src/commonMain/kotlin/mtmc/os/fs/File.kt index eb36031..ee0cadd 100644 --- a/src/commonMain/kotlin/mtmc/os/fs/File.kt +++ b/src/commonMain/kotlin/mtmc/os/fs/File.kt @@ -16,7 +16,7 @@ class File( val name: String ) { - var directory: Boolean = TODO("initialize me") + var directory: Boolean = false constructor(name: String) : this(null, name) @@ -26,9 +26,7 @@ class File( "${parent.getPath()}/$name" } - fun exists(): Boolean { - TODO("Not yet implemented") - } + fun exists(): Boolean = true fun getAbsolutePath(): String { TODO("Not yet implemented") diff --git a/src/commonMain/kotlin/mtmc/util/BinaryUtils.kt b/src/commonMain/kotlin/mtmc/util/BinaryUtils.kt index abb18dd..676c34f 100644 --- a/src/commonMain/kotlin/mtmc/util/BinaryUtils.kt +++ b/src/commonMain/kotlin/mtmc/util/BinaryUtils.kt @@ -11,13 +11,9 @@ object BinaryUtils { return 0 } val returnValue = (value.toInt() and 0xffff) ushr (start - totalBits) - var mask = 0 - var toShift = totalBits - while (toShift > 0) { - toShift-- - mask = mask shl 1 - mask = mask + 1 - } + var mask = 1 + mask = mask shl totalBits + mask-- return (returnValue and mask).toShort() } diff --git a/src/commonMain/kotlin/mtmc/util/PlatformSpecific.kt b/src/commonMain/kotlin/mtmc/util/PlatformSpecific.kt index a0e12a7..df7360e 100644 --- a/src/commonMain/kotlin/mtmc/util/PlatformSpecific.kt +++ b/src/commonMain/kotlin/mtmc/util/PlatformSpecific.kt @@ -4,3 +4,4 @@ expect fun currentTimeMillis(): Long expect fun requestAnimationFrame(action: (Double) -> Unit) +expect fun immediateTimeout(action: (Double) -> Unit): Int diff --git a/src/jsMain/kotlin/mtmc/Main.kt b/src/jsMain/kotlin/mtmc/Main.kt index ef9fb82..cd2030d 100644 --- a/src/jsMain/kotlin/mtmc/Main.kt +++ b/src/jsMain/kotlin/mtmc/Main.kt @@ -1,33 +1,4839 @@ package mtmc import kotlinx.browser.document -import kotlinx.html.div -import kotlinx.html.style import mtmc.emulator.MonTanaMiniComputer import mtmc.view.DisplayView import mtmc.view.MTMCView -import nl.astraeus.komp.HtmlBuilder import nl.astraeus.komp.Komponent -class HelloKomponent : Komponent() { - override fun HtmlBuilder.render() { - div { - style = "color: red;" - + "Hello, world!" - } - } -} - - val computer = MonTanaMiniComputer() val mainView = MTMCView(computer) val display = DisplayView(computer) + +private val snakeCode: ByteArray = arrayOf( + -128, + 0, + 3, + -94, + -128, + 16, + 3, + -92, + -122, + 0, + 3, + -66, + -122, + 16, + 4, + 14, + -16, + 36, + -15, + 6, + -15, + -120, + -14, + -114, + -15, + 78, + -15, + -24, + -14, + -42, + -13, + 58, + -64, + 18, + 0, + 0, + 32, + -67, + -113, + 96, + 5, + -96, + -13, + -110, + -124, + -96, + 3, + -96, + -128, + 0, + 3, + -94, + -128, + 16, + 3, + -92, + -124, + 0, + 3, + -90, + -124, + 16, + 3, + -88, + -122, + 0, + 3, + -66, + -122, + 16, + 4, + 14, + -16, + 82, + -16, + 124, + -14, + -8, + 33, + -67, + -112, + 11, + 32, + -67, + -128, + 0, + 3, + -86, + -128, + 16, + 3, + -84, + 1, + 32, + 18, + 33, + -113, + 48, + 0, + 0, + -113, + 64, + 0, + 0, + -128, + 80, + 3, + -96, + 16, + 83, + -121, + 69, + 0, + 0, + 2, + 49, + 52, + 50, + -32, + 104, + 33, + -67, + -112, + 11, + 32, + -67, + -128, + 0, + 3, + -86, + -128, + 16, + 3, + -84, + -113, + 48, + 0, + 0, + -113, + 64, + 0, + 1, + -128, + 80, + 3, + -96, + 16, + 83, + -121, + 69, + 0, + 0, + 2, + 49, + 52, + 48, + -32, + -114, + -113, + 48, + 0, + 0, + -113, + 64, + 0, + 1, + -128, + 80, + 3, + -96, + 16, + 83, + -121, + 69, + 5, + 120, + 2, + 49, + 52, + 48, + -32, + -90, + -113, + 48, + 0, + 0, + -113, + 64, + 0, + 1, + -113, + 80, + 0, + 0, + -128, + 0, + 3, + -96, + 16, + 5, + -121, + 64, + 0, + 0, + -128, + 0, + 3, + -86, + -128, + 16, + 3, + -84, + 2, + 49, + 16, + 80, + 52, + 49, + -32, + -62, + -113, + 48, + 0, + 0, + -113, + 64, + 0, + 1, + -113, + 80, + 0, + 39, + -128, + 0, + 3, + -96, + 16, + 5, + -121, + 64, + 0, + 0, + -128, + 0, + 3, + -86, + -128, + 16, + 3, + -84, + 2, + 49, + 16, + 80, + 52, + 49, + -32, + -24, + 33, + -67, + -112, + 11, + 32, + -67, + -128, + 0, + 3, + -76, + -128, + 16, + 3, + -74, + -113, + 32, + 0, + 0, + -128, + 48, + 3, + -72, + -125, + 96, + 3, + -66, + -125, + 112, + 4, + 14, + -128, + -128, + 3, + -86, + -128, + -112, + 3, + -84, + 1, + 72, + 18, + 71, + 16, + 70, + 32, + 13, + -128, + 0, + 3, + -96, + 16, + 4, + -113, + 80, + 0, + 1, + -121, + 80, + 0, + 0, + 33, + 13, + 2, + 1, + 20, + 3, + 2, + 33, + 52, + 33, + -31, + 24, + 33, + -67, + -112, + 11, + 32, + -67, + -128, + 0, + 3, + -74, + -128, + 16, + 3, + -68, + 52, + 1, + -31, + -124, + -128, + 80, + 3, + -76, + -125, + 5, + 3, + -66, + -125, + 21, + 4, + 14, + -128, + 32, + 3, + -86, + -128, + 48, + 3, + -84, + 1, + 65, + 18, + 66, + 16, + 64, + -128, + 0, + 3, + -96, + 16, + 4, + -113, + 80, + 0, + 0, + -121, + 80, + 0, + 0, + 33, + -67, + -112, + 11, + 32, + -67, + 0, + 48, + -128, + 0, + 3, + -86, + -128, + 16, + 3, + -84, + -113, + 32, + 0, + 0, + -113, + 48, + 0, + 4, + -113, + -128, + 0, + 4, + -113, + -112, + 0, + 4, + 1, + 80, + 18, + 81, + -128, + 0, + 3, + -86, + 1, + 98, + 20, + 96, + 18, + 99, + 1, + 114, + 19, + 112, + 18, + 115, + -128, + 16, + 3, + -96, + 16, + 18, + -125, + 65, + 0, + 0, + 56, + 65, + -47, + -56, + 0, + 52, + 2, + 33, + 52, + 37, + -31, + -88, + -113, + 96, + 0, + 1, + 0, + 59, + -128, + 96, + 4, + 94, + -128, + 112, + 4, + 96, + 18, + 99, + 18, + 115, + 0, + 52, + 0, + 53, + 33, + -67, + -112, + 11, + 32, + -67, + -128, + 0, + 3, + -90, + -128, + 16, + 3, + -88, + -128, + 80, + 3, + -80, + -128, + 64, + 3, + -104, + 21, + 69, + 57, + 64, + -46, + 4, + 3, + 17, + -62, + 46, + -128, + 64, + 3, + -102, + 21, + 69, + 57, + 64, + -46, + 18, + 2, + 17, + -62, + 46, + -128, + 64, + 3, + -100, + 21, + 69, + 57, + 64, + -46, + 32, + 3, + 1, + -62, + 46, + -128, + 64, + 3, + -98, + 21, + 69, + 57, + 64, + -46, + 46, + 2, + 1, + -62, + 46, + -124, + 0, + 3, + -90, + -124, + 16, + 3, + -88, + -128, + 32, + 3, + -76, + -128, + 48, + 3, + -74, + -128, + 64, + 3, + -72, + 16, + 35, + 20, + 36, + -121, + 2, + 3, + -66, + -121, + 18, + 4, + 14, + -128, + 64, + 3, + -68, + 52, + 52, + -46, + 94, + 2, + 49, + -124, + 48, + 3, + -74, + -62, + 110, + -128, + 32, + 3, + -76, + -128, + 48, + 3, + -72, + 2, + 33, + 20, + 35, + -124, + 32, + 3, + -76, + -128, + 32, + 3, + -86, + -128, + 48, + 3, + -84, + 1, + 65, + 18, + 66, + 16, + 64, + -128, + 16, + 3, + -96, + 16, + 20, + -125, + 81, + 0, + 0, + -124, + 80, + 3, + -78, + 33, + -67, + -112, + 11, + 32, + -67, + -128, + 96, + 3, + -82, + 0, + 34, + 0, + 58, + 1, + 42, + -128, + 48, + 3, + -104, + 21, + 50, + 57, + 48, + -30, + -60, + -128, + 48, + 3, + -102, + 21, + 50, + 57, + 48, + -30, + -60, + -128, + 48, + 3, + -100, + 21, + 50, + 57, + 48, + -30, + -60, + -128, + 48, + 3, + -98, + 21, + 50, + 57, + 48, + -30, + -60, + -62, + -56, + -124, + 48, + 3, + -80, + -128, + 96, + 0, + 0, + 0, + 34, + 56, + -96, + -46, + -106, + 33, + -67, + -112, + 11, + 32, + -67, + -128, + 0, + 3, + -78, + 56, + 1, + -30, + -28, + 33, + -67, + -112, + 11, + -113, + 96, + 4, + 98, + 0, + 6, + -113, + 96, + 4, + -124, + 0, + 6, + -113, + 96, + 4, + 98, + 0, + 6, + 0, + 0, + 32, + -67, + -128, + 96, + 0, + 1, + -128, + 112, + 3, + -86, + 3, + 113, + 0, + 32, + 1, + 10, + -128, + 96, + 0, + 1, + -128, + 112, + 3, + -84, + 3, + 113, + 0, + 32, + 1, + 26, + -128, + 32, + 3, + -86, + 1, + 65, + 18, + 66, + 16, + 64, + -128, + 48, + 3, + -96, + 16, + 52, + -125, + 83, + 0, + 0, + 56, + 81, + -30, + -6, + -124, + 0, + 4, + 94, + -124, + 16, + 4, + 96, + 33, + -67, + -112, + 11, + 32, + -67, + -128, + 0, + 3, + -90, + -128, + 16, + 3, + -88, + -128, + 32, + 4, + 94, + -128, + 48, + 4, + 96, + 48, + 2, + -45, + -114, + 48, + 19, + -45, + -114, + -14, + -8, + -128, + 0, + 3, + -68, + -128, + 16, + 3, + -72, + 52, + 1, + -45, + -114, + -128, + 64, + 3, + -70, + -113, + 80, + 0, + 0, + 52, + 1, + -45, + -114, + 2, + 1, + -124, + 0, + 3, + -68, + 2, + 81, + 52, + 84, + -29, + 106, + 2, + 65, + -124, + 64, + 3, + -70, + -128, + 0, + 3, + -82, + -113, + 16, + 0, + 10, + 17, + 1, + -124, + 0, + 3, + -82, + 33, + -67, + -112, + 11, + 1, + -82, + 16, + -26, + -112, + 11 +).toByteArray() +private val snakeData: ByteArray = arrayOf( + 0, + -128, + 0, + 64, + 0, + 32, + 0, + 16, + -1, + -1, + 0, + 20, + 0, + 18, + 0, + 20, + 0, + 18, + 0, + 40, + 0, + 36, + 0, + -56, + 0, + -128, + 0, + 0, + 0, + 0, + 0, + 1, + 0, + 80, + 0, + 1, + 0, + 3, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 45, + 45, + 45, + 45, + 45, + 45, + 45, + 45, + 45, + 45, + 45, + 45, + 45, + 45, + 45, + 45, + 45, + 45, + 45, + 45, + 45, + 45, + 45, + 45, + 45, + 45, + 45, + 45, + 45, + 45, + 45, + 45, + 10, + 0, + 32, + 32, + 32, + 32, + 32, + 32, + 32, + 32, + 32, + 42, + 42, + 67, + 82, + 65, + 83, + 72, + 42, + 42, + 10, + 32, + 32, + 71, + 97, + 109, + 101, + 32, + 111, + 118, + 101, + 114, + 44, + 32, + 109, + 97, + 110, + 46, + 32, + 71, + 97, + 109, + 101, + 32, + 79, + 118, + 101, + 114, + 33, + 10, + 0, + 32, + 0 +).toByteArray() + +private val lifeCode: ByteArray = arrayOf( + -15, + -116, + -14, + 100, + -16, + 10, + -64, + 2, + 0, + 0, + 32, + -67, + -113, + 96, + 8, + 111, + -113, + 112, + 2, + -49, + -113, + -128, + 5, + -96, + 0, + 64, + 4, + 96, + 4, + 112, + 15, + -1, + -113, + -112, + 2, + -49, + -15, + 68, + 1, + -118, + 32, + -115, + -16, + 70, + 33, + -115, + 1, + -102, + -16, + -86, + 2, + 97, + 31, + 100, + 0, + 40, + -32, + 32, + 2, + 113, + 31, + 116, + 0, + 36, + -32, + 30, + 33, + -67, + -112, + 11, + 32, + -67, + 4, + 0, + 3, + 97, + 3, + 113, + -113, + -128, + 2, + -49, + -15, + 68, + 16, + 10, + 2, + 97, + -113, + -128, + 2, + -49, + -15, + 68, + 16, + 10, + 2, + 97, + -113, + -128, + 2, + -49, + -15, + 68, + 16, + 10, + 3, + 98, + 2, + 113, + -113, + -128, + 2, + -49, + -15, + 68, + 16, + 10, + 2, + 98, + -113, + -128, + 2, + -49, + -15, + 68, + 16, + 10, + 3, + 98, + 2, + 113, + -113, + -128, + 2, + -49, + -15, + 68, + 16, + 10, + 2, + 97, + -113, + -128, + 2, + -49, + -15, + 68, + 16, + 10, + 2, + 97, + -113, + -128, + 2, + -49, + -15, + 68, + 3, + 97, + 3, + 113, + 16, + 10, + 1, + -96, + 33, + -67, + -112, + 11, + 32, + -67, + 4, + 0, + 56, + -128, + -48, + -70, + 56, + -109, + -48, + -60, + 4, + 1, + -64, + -60, + 58, + -111, + -48, + -60, + 60, + -108, + -48, + -60, + 4, + 1, + -113, + -128, + 8, + 111, + 1, + -112, + -15, + 124, + 33, + -67, + -112, + 11, + 60, + 96, + -48, + -40, + 4, + -96, + -112, + 11, + -113, + -96, + 0, + 39, + 50, + 106, + -48, + -28, + 4, + -96, + -112, + 11, + 60, + 112, + -48, + -20, + 4, + -96, + -112, + 11, + -113, + -96, + 0, + 35, + 50, + 122, + -48, + -8, + 4, + -96, + -112, + 11, + 1, + 70, + 1, + 87, + 31, + 82, + 0, + 40, + 16, + 84, + 1, + 69, + 31, + 67, + 0, + 8, + 31, + 84, + 0, + 8, + 90, + -124, + 4, + 65, + 24, + 69, + 21, + -92, + 29, + -96, + 29, + -96, + -112, + 11, + 1, + 6, + 1, + 23, + 31, + 18, + 0, + 40, + 16, + 16, + 1, + 1, + 31, + 3, + 0, + 8, + 31, + 20, + 0, + 8, + 82, + -128, + 4, + 49, + 24, + 49, + 56, + -111, + -47, + 60, + 22, + 35, + -63, + 64, + 28, + 48, + 21, + 35, + 114, + -128, + -112, + 11, + 60, + 96, + -47, + 76, + 4, + -96, + -112, + 11, + -113, + -96, + 0, + 39, + 50, + 106, + -47, + 88, + 4, + -96, + -112, + 11, + 60, + 112, + -47, + 96, + 4, + -96, + -112, + 11, + -113, + -96, + 0, + 35, + 50, + 122, + -47, + 108, + 4, + -96, + -112, + 11, + 1, + 70, + 1, + 87, + 31, + 82, + 0, + 40, + 16, + 84, + 1, + 69, + 90, + -124, + -112, + 11, + 1, + 6, + 1, + 23, + 31, + 18, + 0, + 40, + 16, + 16, + 1, + 1, + 121, + -128, + -112, + 11, + 56, + 96, + -47, + -108, + -113, + 96, + 2, + -80, + -113, + 112, + 8, + 111, + -113, + -128, + 0, + 40, + -113, + -112, + 0, + 36, + 0, + 16, + 56, + -96, + -31, + -82, + -113, + 96, + 2, + -64, + 0, + 6, + 0, + 0, + -113, + 0, + 0, + 0, + -113, + 16, + 0, + 0, + -113, + 32, + 5, + -96, + -125, + 48, + 8, + 111, + -113, + 64, + 0, + 1, + 21, + 67, + -121, + 65, + 2, + -49, + -113, + 64, + 0, + 1, + 25, + 52, + 2, + 17, + -113, + 64, + 0, + 1, + 21, + 67, + -121, + 65, + 2, + -49, + -113, + 64, + 0, + 1, + 25, + 52, + 2, + 17, + -113, + 64, + 0, + 1, + 21, + 67, + -121, + 65, + 2, + -49, + -113, + 64, + 0, + 1, + 25, + 52, + 2, + 17, + -113, + 64, + 0, + 1, + 21, + 67, + -121, + 65, + 2, + -49, + -113, + 64, + 0, + 1, + 25, + 52, + 2, + 17, + -113, + 64, + 0, + 1, + 21, + 67, + -121, + 65, + 2, + -49, + -113, + 64, + 0, + 1, + 25, + 52, + 2, + 17, + -113, + 64, + 0, + 1, + 21, + 67, + -121, + 65, + 2, + -49, + -113, + 64, + 0, + 1, + 25, + 52, + 2, + 17, + -113, + 64, + 0, + 1, + 21, + 67, + -121, + 65, + 2, + -49, + -113, + 64, + 0, + 1, + 25, + 52, + 2, + 17, + -113, + 64, + 0, + 1, + 21, + 67, + -121, + 65, + 2, + -49, + -113, + 64, + 0, + 1, + 25, + 52, + 2, + 17, + 2, + 1, + 52, + 18, + -31, + -70, + -113, + 96, + 2, + -49, + -113, + 112, + 8, + 111, + -113, + -128, + 5, + -96, + 0, + 64, + -112, + 11, + 0, + 48, + 32, + -67, + 4, + 96, + 4, + 112, + 15, + -1, + 32, + 109, + -113, + -128, + 8, + 111, + -15, + 68, + 56, + -96, + -46, + 126, + 4, + 99, + -62, + -128, + 4, + 96, + 0, + 59, + 33, + 109, + 31, + 98, + 0, + 4, + 31, + 114, + 0, + 4, + 4, + -124, + 4, + -108, + 0, + 52, + 31, + 99, + 0, + 4, + 31, + 115, + 0, + 4, + 2, + 97, + 31, + 100, + 0, + 40, + -30, + 110, + 2, + 113, + 31, + 116, + 0, + 36, + -30, + 108, + 0, + 53, + 33, + -67, + -112, + 11 +).toByteArray() +private val lifeData: ByteArray = arrayOf( + 47, + 100, + 97, + 116, + 97, + 47, + 103, + 117, + 110, + 46, + 99, + 101, + 108, + 108, + 115, + 0, + 70, + 73, + 76, + 69, + 32, + 78, + 79, + 84, + 32, + 70, + 79, + 85, + 78, + 68, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 +).toByteArray() + fun main() { computer.speed = 1 // default to 1hz + computer.load(lifeCode, lifeData) Komponent.create(document.body!!, mainView) - //computer.start() + computer.start() + mainView.requestUpdate() } diff --git a/src/jsMain/kotlin/mtmc/emulator/BufferedImage.js.kt b/src/jsMain/kotlin/mtmc/emulator/BufferedImage.js.kt new file mode 100644 index 0000000..ecc5dfc --- /dev/null +++ b/src/jsMain/kotlin/mtmc/emulator/BufferedImage.js.kt @@ -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() + } +} + diff --git a/src/jsMain/kotlin/mtmc/util/PlatformSpecific.js.kt b/src/jsMain/kotlin/mtmc/util/PlatformSpecific.js.kt index 43d6fbd..f1e0683 100644 --- a/src/jsMain/kotlin/mtmc/util/PlatformSpecific.js.kt +++ b/src/jsMain/kotlin/mtmc/util/PlatformSpecific.js.kt @@ -1,9 +1,22 @@ package mtmc.util import kotlinx.browser.window +import mtmc.mainView import kotlin.js.Date +var lastMemoryUpdate = currentTimeMillis() + actual fun currentTimeMillis(): Long = Date().getTime().toLong() 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) diff --git a/src/jsMain/kotlin/mtmc/view/ConsoleView.kt b/src/jsMain/kotlin/mtmc/view/ConsoleView.kt index f1d83a8..0c6d7f1 100644 --- a/src/jsMain/kotlin/mtmc/view/ConsoleView.kt +++ b/src/jsMain/kotlin/mtmc/view/ConsoleView.kt @@ -24,16 +24,12 @@ class ConsoleView( override fun HtmlBuilder.render() { div("console") { - +"Console view" - onClickFunction = { inputElement?.focus() } div("console-history") { - for (line in history) { - div { - +line - } + div { + +computer.console.getOutput() } } div("console-input") { @@ -64,7 +60,7 @@ class ConsoleView( } private fun handleCommand() { - history.add(input) + //history.add(input) Shell.execCommand(input, computer) diff --git a/src/jsMain/kotlin/mtmc/view/MemoryView.kt b/src/jsMain/kotlin/mtmc/view/MemoryView.kt index 4a2e294..67faec1 100644 --- a/src/jsMain/kotlin/mtmc/view/MemoryView.kt +++ b/src/jsMain/kotlin/mtmc/view/MemoryView.kt @@ -1,21 +1,51 @@ package mtmc.view +import kotlinx.html.classes import kotlinx.html.div import kotlinx.html.table import kotlinx.html.td +import kotlinx.html.title import kotlinx.html.tr +import mtmc.asm.instructions.Instruction import mtmc.emulator.MonTanaMiniComputer +import mtmc.emulator.Register import nl.astraeus.komp.HtmlBuilder import nl.astraeus.komp.Komponent fun ByteArray.asHex(address: Int): String { - val value = this[address].toInt() + this[address + 1].toInt() * 256 - return value.toShort().toHexString() + val value = getShort(address) + 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( + "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( val computer: MonTanaMiniComputer ) : Komponent() { + var displayFormat: DisplayFormat = DisplayFormat.DYN override fun HtmlBuilder.render() { div("memory-panel") { @@ -24,12 +54,42 @@ class MemoryView( } div("memory") { table { - for (address in 0.. 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( } } } -} \ No newline at end of file + + 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() + } + +} diff --git a/src/jsMain/kotlin/mtmc/view/RegisterView.kt b/src/jsMain/kotlin/mtmc/view/RegisterView.kt index e002797..4477d14 100644 --- a/src/jsMain/kotlin/mtmc/view/RegisterView.kt +++ b/src/jsMain/kotlin/mtmc/view/RegisterView.kt @@ -1,9 +1,13 @@ package mtmc.view +import kotlinx.browser.document +import kotlinx.dom.addClass +import kotlinx.dom.removeClass import kotlinx.html.TABLE import kotlinx.html.classes import kotlinx.html.div import kotlinx.html.hr +import kotlinx.html.id import kotlinx.html.table import kotlinx.html.td import kotlinx.html.tr @@ -11,6 +15,7 @@ import mtmc.emulator.MonTanaMiniComputer import mtmc.emulator.Register import nl.astraeus.komp.HtmlBuilder import nl.astraeus.komp.Komponent +import org.w3c.dom.HTMLElement class RegisterView( 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) { val name = Register.fromInteger(register) val value = computer.registerFile[register] @@ -50,6 +71,7 @@ class RegisterView( td("register-lights") { for (bit in 15 downTo 0) { div("blinken") { + id = "bit-$bit-$register" if (value.toInt() and (1 shl bit) == 0) { classes += "off" } @@ -60,7 +82,8 @@ class RegisterView( } } td("align-right") { - +"$value" + id = "register-$register" + +"${value.toInt() and 0xffff}" } } } diff --git a/src/jsMain/resources/mtmc.css b/src/jsMain/resources/mtmc.css index feb6039..b0cf64b 100644 --- a/src/jsMain/resources/mtmc.css +++ b/src/jsMain/resources/mtmc.css @@ -63,8 +63,9 @@ table.register-table { border-radius: 8px; } -table.register-table tr td .align-right { +table.register-table tr td.align-right { text-align: right; + min-width: 100px; } table.register-table tr td.register-lights { @@ -106,10 +107,34 @@ table.register-table tr td.register-lights { } .memory { - font-family: monospace; 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 { @@ -117,6 +142,7 @@ table.register-table tr td.register-lights { } .display-canvas { + padding: 24px 64px; width: 320px; height: 288px; image-rendering: pixelated; /* Keeps sharp pixels, no smoothing */ diff --git a/src/jvmMain/kotlin/mtmc/emulator/BufferedImage.jvm.kt b/src/jvmMain/kotlin/mtmc/emulator/BufferedImage.jvm.kt new file mode 100644 index 0000000..2abb527 --- /dev/null +++ b/src/jvmMain/kotlin/mtmc/emulator/BufferedImage.jvm.kt @@ -0,0 +1,5 @@ +package mtmc.emulator + +actual fun createBufferedImage(width: Int, height: Int): BufferedImage { + TODO("Not yet implemented") +} \ No newline at end of file diff --git a/src/jvmMain/kotlin/mtmc/util/PlatformSpecific.jvm.kt b/src/jvmMain/kotlin/mtmc/util/PlatformSpecific.jvm.kt index 6592a46..39e911a 100644 --- a/src/jvmMain/kotlin/mtmc/util/PlatformSpecific.jvm.kt +++ b/src/jvmMain/kotlin/mtmc/util/PlatformSpecific.jvm.kt @@ -4,3 +4,5 @@ actual fun currentTimeMillis(): Long = System.currentTimeMillis() actual fun requestAnimationFrame(action: (Double) -> Unit) { error("requestAnimationFrame is not supported on JVM") } + +actual fun immediateTimeout(action: (Double) -> Unit): Int {} \ No newline at end of file