Refactor rewind functionality with circular buffer, update BufferedImageData handling in DisplayView, enhance ConsoleView rendering updates, and integrate SnakeCode data.

This commit is contained in:
2025-08-17 20:32:12 +02:00
parent f169dce339
commit 37691dc7fa
15 changed files with 4916 additions and 4854 deletions

View File

@@ -67,7 +67,7 @@ kotlin {
} }
val jsMain by getting { val jsMain by getting {
dependencies { dependencies {
implementation("nl.astraeus:kotlin-komponent:1.2.5") implementation("nl.astraeus:kotlin-komponent:1.2.8")
} }
} }
val jsTest by getting val jsTest by getting

View File

@@ -57,7 +57,7 @@ abstract class Instruction(
if (ls != null) { if (ls != null) {
return ls return ls
} }
val jumpReg = JumpInstruction.disassemble(instruction) val jumpReg = JumpRegisterInstruction.disassemble(instruction)
if (jumpReg != null) { if (jumpReg != null) {
return jumpReg return jumpReg
} }
@@ -65,7 +65,7 @@ abstract class Instruction(
if (jump != null) { if (jump != null) {
return jump return jump
} }
return "<unknown>" return ""
} }
} }
} }

View File

@@ -26,10 +26,16 @@ class JumpRegisterInstruction(
companion object { companion object {
fun disassemble(instruction: Short): String? { fun disassemble(instruction: Short): String? {
if (BinaryUtils.getBits(16, 5, instruction).toInt() == 9) { if (BinaryUtils.getBits(16, 4, instruction).toInt() == 9) {
val reg = BinaryUtils.getBits(4, 4, instruction) val reg = BinaryUtils.getBits(4, 4, instruction)
val sb = StringBuilder("jr") if (reg.toInt() == 11) {
sb.append(fromInteger(reg.toInt())) return "ret"
} else {
val sb = StringBuilder("jr")
sb.append(" ")
sb.append(fromInteger(reg.toInt()))
return sb.toString()
}
} }
return null return null
} }

View File

@@ -2,6 +2,8 @@ package mtmc.emulator
expect fun createBufferedImage(width: Int, height: Int): BufferedImage expect fun createBufferedImage(width: Int, height: Int): BufferedImage
expect fun createCanvasImage(width: Int, height: Int): BufferedImage
interface BufferedImage { interface BufferedImage {
val width: Int val width: Int
val height: Int val height: Int

View File

@@ -3,7 +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? = null val buffer: BufferedImage = createCanvasImage(COLS, ROWS)
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)
@@ -107,11 +107,11 @@ 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) ?: 0 val rgb = buffer.getRGB(col, row)
return DisplayColor.indexFromInt(rgb) return DisplayColor.indexFromInt(rgb)
} }
@@ -123,10 +123,11 @@ class MTMCDisplay(private val computer: MonTanaMiniComputer) {
} }
fun drawRectangle(startCol: Short, startRow: Short, width: Short, height: Short) { fun drawRectangle(startCol: Short, startRow: Short, width: Short, height: Short) {
// val graphics = buffer.getGraphics() for (x in startCol..<startCol + width) {
// graphics.setColor(currentColor!!.javaColor) for (y in startRow..<startRow + height) {
// graphics.fillRect(startCol.toInt(), startRow.toInt(), width.toInt(), height.toInt()) setPixel(x, y, currentColor!!)
// graphics.dispose() }
}
} }
fun drawImage(image: Int, x: Int, y: Int) { fun drawImage(image: Int, x: Int, y: Int) {

View File

@@ -31,7 +31,9 @@ class MonTanaMiniComputer {
var display: MTMCDisplay = MTMCDisplay(this) var display: MTMCDisplay = MTMCDisplay(this)
var clock: MTMCClock = MTMCClock(this) var clock: MTMCClock = MTMCClock(this)
var fileSystem: FileSystem = FileSystem(this) var fileSystem: FileSystem = FileSystem(this)
var rewindSteps = mutableListOf<RewindStep>()
var rewindSteps = Array<RewindStep?>(MAX_REWIND_STEPS) { null }
var rewindIndex = -1
// listeners // listeners
private val observers = mutableListOf<MTMCObserver>() private val observers = mutableListOf<MTMCObserver>()
@@ -46,12 +48,12 @@ class MonTanaMiniComputer {
registerFile = ShortArray(Register.entries.size) registerFile = ShortArray(Register.entries.size)
memory = ByteArray(MEMORY_SIZE) memory = ByteArray(MEMORY_SIZE)
breakpoints = ByteArray(MEMORY_SIZE) breakpoints = ByteArray(MEMORY_SIZE)
rewindSteps.clear() rewindIndex = -1
setRegisterValue( setRegisterValue(
Register.SP, Register.SP,
MEMORY_SIZE.toShort().toInt() MEMORY_SIZE.toShort().toInt()
) // default the stack pointer to the top of memory ) // default the stack pointer to the top of memory
rewindSteps.clear() rewindIndex = -1
observers.forEach { obj -> observers.forEach { obj ->
obj.computerReset() obj.computerReset()
} }
@@ -134,7 +136,8 @@ class MonTanaMiniComputer {
fun fetchAndExecute() { fun fetchAndExecute() {
currentRewindStep = RewindStep() currentRewindStep = RewindStep()
currentRewindStep?.let { currentRewindStep?.let {
rewindSteps.add(0, it) rewindIndex = (rewindIndex + 1) % rewindSteps.size
rewindSteps.set(rewindIndex, it)
} }
fetchCurrentInstruction() fetchCurrentInstruction()
val instruction = getRegisterValue(Register.IR) val instruction = getRegisterValue(Register.IR)
@@ -795,11 +798,8 @@ class MonTanaMiniComputer {
} }
private fun addRewindStep(runnable: Runnable?) { private fun addRewindStep(runnable: Runnable?) {
if (currentRewindStep != null && rewindSteps != null) { if (currentRewindStep != null) {
currentRewindStep!!.addSubStep(runnable) currentRewindStep!!.addSubStep(runnable)
if (rewindSteps.size > MAX_REWIND_STEPS) {
rewindSteps.removeLast()
}
} }
} }
@@ -954,12 +954,15 @@ class MonTanaMiniComputer {
} }
fun rewind() { fun rewind() {
val latestRewindStep = rewindSteps.removeFirst() if (rewindIndex >= 0) {
latestRewindStep.rewind() val latestRewindStep = rewindSteps[rewindIndex]
rewindIndex--
latestRewindStep?.rewind()
}
} }
val isBackAvailable: Boolean val isBackAvailable: Boolean
get() = !rewindSteps!!.isEmpty() get() = rewindIndex >= 0
enum class ComputerStatus { enum class ComputerStatus {
READY, READY,

View File

@@ -3,7 +3,7 @@ package mtmc.emulator
import mtmc.util.Runnable import mtmc.util.Runnable
class RewindStep { class RewindStep {
var subSteps: MutableList<Runnable?> = ArrayList<Runnable?>() var subSteps: MutableList<Runnable?> = mutableListOf()
fun rewind() { fun rewind() {
subSteps.reversed().forEach({ obj: Runnable? -> obj!!.invoke() }) subSteps.reversed().forEach({ obj: Runnable? -> obj!!.invoke() })

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,12 @@
package mtmc.emulator package mtmc.emulator
import kotlinx.browser.document
import org.khronos.webgl.get
import org.khronos.webgl.set
import org.w3c.dom.CanvasRenderingContext2D
import org.w3c.dom.HTMLCanvasElement
import org.w3c.dom.ImageData
actual fun createBufferedImage( actual fun createBufferedImage(
width: Int, width: Int,
height: Int height: Int
@@ -25,10 +32,44 @@ class BufferedImageImpl(
check(x in 0 until width && y in 0 until height) check(x in 0 until width && y in 0 until height)
val offset = (x + y * width) * 4 val offset = (x + y * width) * 4
display[offset + 0] = intVal.toByte() display[offset + 0] = (intVal shr 16).toByte()
display[offset + 1] = (intVal shr 8).toByte() display[offset + 1] = (intVal shr 8).toByte()
display[offset + 2] = (intVal shr 16).toByte() display[offset + 2] = intVal.toByte()
display[offset + 3] = 255.toByte() display[offset + 3] = 255.toByte()
} }
} }
class BufferedImageData(
val imageData: ImageData,
) : BufferedImage {
override val width: Int = imageData.width
override val height: Int = imageData.height
override fun getRGB(x: Int, y: Int): Int {
check(x in 0 until width && y in 0 until height)
val offset = (x * 4 + y * width * 4)
val display = imageData.data
return (display[offset + 0].toInt() and 0xff) shl 16 +
(display[offset + 1].toInt() and 0xff) shl 8 +
(display[offset + 2].toInt() and 0xff)
}
override fun setRGB(x: Int, y: Int, intVal: Int) {
check(x in 0 until width && y in 0 until height)
val offset = (x * 4 + y * width * 4)
val display = imageData.data
display[offset + 0] = ((intVal shr 16) and 0xff).toShort().asDynamic()
display[offset + 1] = ((intVal shr 8) and 0xff).toShort().asDynamic()
display[offset + 2] = (intVal and 0xff).toShort().asDynamic()
display[offset + 3] = 255.toShort().asDynamic()
}
}
val canvas = document.createElement("canvas") as HTMLCanvasElement
val ctx = canvas.getContext("2d") as CanvasRenderingContext2D
actual fun createCanvasImage(width: Int, height: Int): BufferedImage {
return BufferedImageData(ctx.createImageData(width.toDouble(), height.toDouble()));
}

View File

@@ -1,6 +1,7 @@
package mtmc.util package mtmc.util
import kotlinx.browser.window import kotlinx.browser.window
import mtmc.display
import mtmc.mainView import mtmc.mainView
import kotlin.js.Date import kotlin.js.Date
@@ -11,8 +12,9 @@ actual fun requestAnimationFrame(action: (Double) -> Unit) {
window.requestAnimationFrame { window.requestAnimationFrame {
action(it) action(it)
mainView.registerView.requestUpdate() display.requestUpdate()
if (currentTimeMillis() - lastMemoryUpdate > 100) { if (currentTimeMillis() - lastMemoryUpdate > 100) {
mainView.registerView.requestUpdate()
mainView.memoryView.requestUpdate() mainView.memoryView.requestUpdate()
lastMemoryUpdate = currentTimeMillis() lastMemoryUpdate = currentTimeMillis()
} }

View File

@@ -6,6 +6,7 @@ import kotlinx.html.input
import kotlinx.html.js.onClickFunction import kotlinx.html.js.onClickFunction
import kotlinx.html.js.onKeyUpFunction import kotlinx.html.js.onKeyUpFunction
import kotlinx.html.span import kotlinx.html.span
import mtmc.display
import mtmc.emulator.MonTanaMiniComputer import mtmc.emulator.MonTanaMiniComputer
import mtmc.mainView import mtmc.mainView
import mtmc.os.shell.Shell import mtmc.os.shell.Shell
@@ -65,7 +66,10 @@ class ConsoleView(
Shell.execCommand(input, computer) Shell.execCommand(input, computer)
input = "" input = ""
mainView.requestUpdate() mainView.registerView.requestUpdate()
mainView.memoryView.requestUpdate()
display.requestUpdate()
requestUpdate()
} }
} }

View File

@@ -3,13 +3,13 @@ package mtmc.view
import kotlinx.html.canvas import kotlinx.html.canvas
import kotlinx.html.div import kotlinx.html.div
import mtmc.display import mtmc.display
import mtmc.emulator.BufferedImageData
import mtmc.emulator.MonTanaMiniComputer import mtmc.emulator.MonTanaMiniComputer
import nl.astraeus.komp.HtmlBuilder import nl.astraeus.komp.HtmlBuilder
import nl.astraeus.komp.Komponent import nl.astraeus.komp.Komponent
import nl.astraeus.komp.currentElement import nl.astraeus.komp.currentElement
import org.w3c.dom.CanvasRenderingContext2D import org.w3c.dom.CanvasRenderingContext2D
import org.w3c.dom.HTMLCanvasElement import org.w3c.dom.HTMLCanvasElement
import org.w3c.dom.ImageData
class DiplayControlView( class DiplayControlView(
val computer: MonTanaMiniComputer val computer: MonTanaMiniComputer
@@ -26,7 +26,6 @@ class DisplayView(
val computer: MonTanaMiniComputer val computer: MonTanaMiniComputer
) : Komponent() { ) : Komponent() {
var ctx: CanvasRenderingContext2D? = null var ctx: CanvasRenderingContext2D? = null
var imageData: ImageData? = null
override fun HtmlBuilder.render() { override fun HtmlBuilder.render() {
canvas("display-canvas") { canvas("display-canvas") {
@@ -37,16 +36,16 @@ class DisplayView(
ctx = cv?.getContext("2d")?.unsafeCast<CanvasRenderingContext2D>() ctx = cv?.getContext("2d")?.unsafeCast<CanvasRenderingContext2D>()
ctx?.fillStyle = "#404040" ctx?.fillStyle = "#400040"
ctx?.fillRect(0.0, 0.0, 160.0, 144.0) ctx?.fillRect(0.0, 0.0, 160.0, 144.0)
imageData = ctx?.getImageData(0.0, 0.0, 160.0, 144.0)
} }
} }
override fun renderUpdate() { override fun renderUpdate() {
// move data to canvas // move data to canvas
imageData?.let { id -> val buffer = computer.display.buffer
ctx?.putImageData(id, 0.0, 0.0) if (buffer is BufferedImageData) {
ctx?.putImageData(buffer.imageData, 0.0, 0.0)
} }
} }
} }

View File

@@ -2,4 +2,8 @@ package mtmc.emulator
actual fun createBufferedImage(width: Int, height: Int): BufferedImage { actual fun createBufferedImage(width: Int, height: Int): BufferedImage {
TODO("Not yet implemented") TODO("Not yet implemented")
}
actual fun createCanvasImage(width: Int, height: Int): BufferedImage {
TODO("Not yet implemented")
} }

View File

@@ -5,4 +5,4 @@ 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 {} actual fun immediateTimeout(action: (Double) -> Unit): Int = 0