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 {
dependencies {
implementation("nl.astraeus:kotlin-komponent:1.2.5")
implementation("nl.astraeus:kotlin-komponent:1.2.8")
}
}
val jsTest by getting

View File

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

View File

@@ -26,10 +26,16 @@ class JumpRegisterInstruction(
companion object {
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 sb = StringBuilder("jr")
sb.append(fromInteger(reg.toInt()))
if (reg.toInt() == 11) {
return "ret"
} else {
val sb = StringBuilder("jr")
sb.append(" ")
sb.append(fromInteger(reg.toInt()))
return sb.toString()
}
}
return null
}

View File

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

View File

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

View File

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

View File

@@ -3,7 +3,7 @@ package mtmc.emulator
import mtmc.util.Runnable
class RewindStep {
var subSteps: MutableList<Runnable?> = ArrayList<Runnable?>()
var subSteps: MutableList<Runnable?> = mutableListOf()
fun rewind() {
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
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(
width: Int,
height: Int
@@ -25,10 +32,44 @@ class BufferedImageImpl(
check(x in 0 until width && y in 0 until height)
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 + 2] = (intVal shr 16).toByte()
display[offset + 2] = intVal.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
import kotlinx.browser.window
import mtmc.display
import mtmc.mainView
import kotlin.js.Date
@@ -11,8 +12,9 @@ actual fun requestAnimationFrame(action: (Double) -> Unit) {
window.requestAnimationFrame {
action(it)
mainView.registerView.requestUpdate()
display.requestUpdate()
if (currentTimeMillis() - lastMemoryUpdate > 100) {
mainView.registerView.requestUpdate()
mainView.memoryView.requestUpdate()
lastMemoryUpdate = currentTimeMillis()
}

View File

@@ -6,6 +6,7 @@ import kotlinx.html.input
import kotlinx.html.js.onClickFunction
import kotlinx.html.js.onKeyUpFunction
import kotlinx.html.span
import mtmc.display
import mtmc.emulator.MonTanaMiniComputer
import mtmc.mainView
import mtmc.os.shell.Shell
@@ -65,7 +66,10 @@ class ConsoleView(
Shell.execCommand(input, computer)
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.div
import mtmc.display
import mtmc.emulator.BufferedImageData
import mtmc.emulator.MonTanaMiniComputer
import nl.astraeus.komp.HtmlBuilder
import nl.astraeus.komp.Komponent
import nl.astraeus.komp.currentElement
import org.w3c.dom.CanvasRenderingContext2D
import org.w3c.dom.HTMLCanvasElement
import org.w3c.dom.ImageData
class DiplayControlView(
val computer: MonTanaMiniComputer
@@ -26,7 +26,6 @@ class DisplayView(
val computer: MonTanaMiniComputer
) : Komponent() {
var ctx: CanvasRenderingContext2D? = null
var imageData: ImageData? = null
override fun HtmlBuilder.render() {
canvas("display-canvas") {
@@ -37,16 +36,16 @@ class DisplayView(
ctx = cv?.getContext("2d")?.unsafeCast<CanvasRenderingContext2D>()
ctx?.fillStyle = "#404040"
ctx?.fillStyle = "#400040"
ctx?.fillRect(0.0, 0.0, 160.0, 144.0)
imageData = ctx?.getImageData(0.0, 0.0, 160.0, 144.0)
}
}
override fun renderUpdate() {
// move data to canvas
imageData?.let { id ->
ctx?.putImageData(id, 0.0, 0.0)
val buffer = computer.display.buffer
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 {
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")
}
actual fun immediateTimeout(action: (Double) -> Unit): Int {}
actual fun immediateTimeout(action: (Double) -> Unit): Int = 0