Compare commits

...

5 Commits

24 changed files with 2286 additions and 84 deletions

13
LICENSE.txt Normal file
View File

@@ -0,0 +1,13 @@
Zero-Clause BSD
=============
Permission to use, copy, modify, and/or distribute this software for
any purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL
WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE
FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY
DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

View File

@@ -1,5 +1,6 @@
@file:OptIn(ExperimentalDistributionDsl::class)
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalDistributionDsl
plugins {
@@ -21,7 +22,15 @@ repositories {
kotlin {
jvmToolchain(21)
jvm()
jvm {
@OptIn(ExperimentalKotlinGradlePluginApi::class)
binaries {
// Configures a JavaExec task named "runJvm" and a Gradle distribution for the "main" compilation in this target
executable {
mainClass.set("mtmc.MainKt")
}
}
}
js {
binaries.executable()
browser {
@@ -40,9 +49,6 @@ kotlin {
val commonMain by getting {
dependencies {
api("nl.astraeus:kotlin-simple-logging:1.1.1")
api("nl.astraeus:kotlin-css-generator:1.0.10")
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.6.0")
}
}
val commonTest by getting
@@ -72,4 +78,4 @@ kotlin {
}
val jsTest by getting
}
}
}

View File

@@ -1,6 +1,6 @@
#Sun Apr 28 09:54:33 CEST 2024
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

@@ -4,6 +4,8 @@ expect fun createBufferedImage(width: Int, height: Int): BufferedImage
expect fun createCanvasImage(width: Int, height: Int): BufferedImage
expect fun createGLImage(width: Int, height: Int): BufferedImage
interface BufferedImage {
val width: Int
val height: Int

View File

@@ -3,6 +3,8 @@ package mtmc.emulator
import mtmc.emulator.MonTanaMiniComputer.ComputerStatus
import mtmc.util.currentTimeMillis
import mtmc.util.requestAnimationFrame
import mtmc.util.setTimeout
import mtmc.util.time
import kotlin.math.max
import kotlin.math.min
@@ -27,21 +29,22 @@ class MTMCClock(
var frame = 0
fun run() {
requestAnimationFrame { handleFrame(it) }
requestAnimationFrame { handleFrame() }
}
fun handleFrame(time: Double) {
fun handleFrame() {
// figure out how many instructions to execute this 'time' duration
// maximize time so we don't hang if the emulator is too slow
val time = time()
if (lastFrame == 0.0) {
lastFrame = time
requestAnimationFrame { handleFrame(it) }
requestAnimationFrame { handleFrame() }
return
}
val delta = time - lastFrame
lastFrame = time
val actualTime = min(delta / 1000.0, 0.05)
val timeToProcess = min(delta, 16.0)
// assume 1Hz = 1 instruction/second
if (computer.getStatus() == ComputerStatus.EXECUTING) {
@@ -50,22 +53,28 @@ class MTMCClock(
speed = 1L
}
instructionsToRun += actualTime * speed
val pulse: Long = instructionsToRun.toLong()
instructionsToRun += timeToProcess * speed / 1000.0
val pulse: Long = instructionsToRun.toLong() + 1
instructionsToRun -= pulse
val time = currentTimeMillis()
val start = time()
val ir = computer.pulse(pulse)
instructions += ir
val duration = (time() - start)
val actual = ir / (delta / 1000.0)
if (frame % 100 == 0) {
println("Instructions ran: $ir (delta = $delta, actualTime = $actualTime, speed = $speed, duration = ${currentTimeMillis() - time})")
println("Instructions ran: $ir (delta = ${delta.toFloat()}, timeToProcess = ${timeToProcess.toFloat()}, speed = $speed (actual=${actual.toLong()}), duration = $duration)")
}
virtual += instructions
ips = (instructions / actualTime).toLong()
ips = (instructions / timeToProcess).toLong()
frame++
requestAnimationFrame { handleFrame(it) }
if (duration > timeToProcess) {
setTimeout({ handleFrame() })
} else {
requestAnimationFrame { handleFrame() }
}
}
//println("Executed " + instructions + " instructions at a rate of " + ips + " ips (speed = " + speed + ")")

View File

@@ -10,7 +10,7 @@ class MTMCConsole(private val computer: MonTanaMiniComputer) {
var sysConsole: Console? = null
// non-interactive data
private val output = StringBuilder()
private var output = StringBuilder()
private var shortValueSet = false
private var shortValue: Short = 0
private var stringValue: String? = null
@@ -83,7 +83,9 @@ class MTMCConsole(private val computer: MonTanaMiniComputer) {
val text = if (index >= 0) output.substring(0, index + 1) else ""
if (index >= 0) {
output.removeRange(0, index + 1)
val updated = StringBuilder()
updated.append(output.removeRange(0, index + 1))
output = updated
}
return text
@@ -116,7 +118,7 @@ class MTMCConsole(private val computer: MonTanaMiniComputer) {
}
fun resetOutput() {
output.removeRange(0, output.length)
output.clear()
}
enum class Mode {

View File

@@ -3,7 +3,7 @@ package mtmc.emulator
import kotlin.math.min
class MTMCDisplay(private val computer: MonTanaMiniComputer) {
val buffer: BufferedImage = createCanvasImage(COLS, ROWS)
val buffer: BufferedImage = createGLImage(COLS, ROWS)
private var currentColor: DisplayColor? = null
private var graphics: Array<BufferedImage> = arrayOf()
private var byteArray: ByteArray = ByteArray(0)
@@ -18,13 +18,8 @@ class MTMCDisplay(private val computer: MonTanaMiniComputer) {
LIGHT(87, 124, 68),
LIGHTEST(127, 134, 15);
val intVal: Int
val javaColor: Color
init {
this.intVal = 0xFF shl 24 or (r shl 16) or (g shl 8) or b
javaColor = Color(r, g, b)
}
val intVal: Int = 0xFF shl 24 or (r shl 16) or (g shl 8) or b
val javaColor: Color = Color(r, g, b)
fun distance(r: Int, g: Int, b: Int): Int {
val dr = this.r - r
@@ -56,15 +51,20 @@ class MTMCDisplay(private val computer: MonTanaMiniComputer) {
}
private fun loadSplashScreen() {
/* try {
val bytes: ByteArray = Base64.getDecoder().decode(SPLASH_SCREEN)
val bais = ByteArrayInputStream(bytes)
var img: BufferedImage? = null
img = ImageIO.read(bais)
loadScaledImage(img)
} catch (e: IOException) {
e.printStackTrace()
}*/
currentColor = DisplayColor.DARK
var currentColorCount = 0
var currentColor = 0
var colorIndex = 0
for (col in 0..<COLS) {
for (row in 0..<ROWS) {
if (currentColorCount == 0) {
currentColorCount = SPLASH_SCREEN_COLORS[colorIndex++]
currentColor = SPLASH_SCREEN_COLORS[colorIndex++]
}
setPixel(col, row, DisplayColor.entries[currentColor])
currentColorCount--
}
}
}
private fun loadImage(data: ByteArray): BufferedImage? {
@@ -107,7 +107,11 @@ class MTMCDisplay(private val computer: MonTanaMiniComputer) {
}
fun setPixel(col: Int, row: Int, color: DisplayColor) {
buffer.setRGB(col, row, color.intVal)
try {
buffer.setRGB(col, row, color.intVal)
} catch (e: Throwable) {
throw e
}
}
fun getPixel(col: Int, row: Int): Short {
@@ -173,7 +177,7 @@ class MTMCDisplay(private val computer: MonTanaMiniComputer) {
//computer.notifyOfDisplayUpdate()
}
fun toPng(): ByteArray? {
fun toPng(): ByteArray {
return byteArray
}

View File

@@ -32,7 +32,7 @@ class MonTanaMiniComputer {
var clock: MTMCClock = MTMCClock(this)
var fileSystem: FileSystem = FileSystem(this)
var rewindSteps = Array<RewindStep?>(MAX_REWIND_STEPS) { null }
var rewindSteps = Array(MAX_REWIND_STEPS) { RewindStep() }
var rewindIndex = -1
// listeners
@@ -48,7 +48,6 @@ class MonTanaMiniComputer {
registerFile = ShortArray(Register.entries.size)
memory = ByteArray(MEMORY_SIZE)
breakpoints = ByteArray(MEMORY_SIZE)
rewindIndex = -1
setRegisterValue(
Register.SP,
MEMORY_SIZE.toShort().toInt()
@@ -134,11 +133,9 @@ class MonTanaMiniComputer {
}
fun fetchAndExecute() {
currentRewindStep = RewindStep()
currentRewindStep?.let {
rewindIndex = (rewindIndex + 1) % rewindSteps.size
rewindSteps.set(rewindIndex, it)
}
rewindIndex = (rewindIndex + 1) % rewindSteps.size
rewindSteps[rewindIndex].index = 0
fetchCurrentInstruction()
val instruction = getRegisterValue(Register.IR)
if (isDoubleWordInstruction(instruction)) {
@@ -792,7 +789,7 @@ class MonTanaMiniComputer {
val currentValue = memory[address]
addRewindStep { memory[address] = currentValue }
memory[address] = value
observers!!.forEach { o: MTMCObserver? ->
observers.forEach { o: MTMCObserver? ->
o!!.memoryUpdated(address, value)
}
}
@@ -816,7 +813,7 @@ class MonTanaMiniComputer {
registerFile[register] = currentValue
}
registerFile[register] = value.toShort()
observers!!.forEach { o: MTMCObserver? ->
observers.forEach { o: MTMCObserver? ->
o!!.registerUpdated(register, value)
}
}

View File

@@ -3,13 +3,16 @@ package mtmc.emulator
import mtmc.util.Runnable
class RewindStep {
var subSteps: MutableList<Runnable?> = mutableListOf()
var subSteps: Array<Runnable> = Array(10) { {} }
var index = 0
fun rewind() {
subSteps.reversed().forEach({ obj: Runnable? -> obj!!.invoke() })
}
fun addSubStep(subStep: Runnable?) {
subSteps.add(subStep)
if (subStep != null && index < subSteps.size) {
subSteps[index++] = subStep
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -12,7 +12,7 @@ import kotlin.math.min
import kotlin.random.Random
class MTOS(private val computer: MonTanaMiniComputer) {
private var timer: Long = 0
private var timer: Double = 0.0
var random: Random = Random.Default
// Editor support
@@ -340,7 +340,7 @@ class MTOS(private val computer: MonTanaMiniComputer) {
computer.setRegisterValue(
Register.RV,
max(0, this.timer - currentTimeMillis()).toInt()
max(0.0, this.timer - currentTimeMillis()).toInt()
)
} else if (syscallNumber == getValue("drawimg").toShort()) {
val image = computer.getRegisterValue(Register.A0)

View File

@@ -1,7 +1,11 @@
package mtmc.util
expect fun currentTimeMillis(): Long
expect fun currentTimeMillis(): Double
expect fun time(): Double
expect fun requestAnimationFrame(action: (Double) -> Unit)
expect fun setTimeout(action: () -> Unit)
expect fun immediateTimeout(action: (Double) -> Unit): Int

View File

@@ -1,7 +1,9 @@
package mtmc
import kotlinx.browser.document
import kotlinx.browser.window
import mtmc.emulator.MonTanaMiniComputer
import mtmc.util.currentTimeMillis
import mtmc.view.DisplayView
import mtmc.view.MTMCView
import nl.astraeus.komp.Komponent
@@ -11,12 +13,31 @@ val mainView = MTMCView(computer)
val display = DisplayView(computer)
fun main() {
computer.speed = 2000000 // default to 1hz
computer.speed = 1000000
computer.load(lifeCode, lifeData)
//computer.load(snakeCode, snakeData)
Komponent.create(document.body!!, mainView)
computer.start()
mainView.requestUpdate()
display.requestUpdate()
window.requestAnimationFrame { updateJsDisplay() }
}
var lastMemoryUpdate = currentTimeMillis()
var updateState = true
fun updateJsDisplay() {
display.requestUpdate()
if (currentTimeMillis() - lastMemoryUpdate > 125) {
if (updateState) {
mainView.registerView.requestUpdate()
mainView.memoryView.requestUpdate()
}
lastMemoryUpdate = currentTimeMillis()
}
window.requestAnimationFrame { updateJsDisplay() }
}

View File

@@ -1,6 +1,7 @@
package mtmc.emulator
import kotlinx.browser.document
import org.khronos.webgl.Uint8Array
import org.khronos.webgl.get
import org.khronos.webgl.set
import org.w3c.dom.CanvasRenderingContext2D
@@ -67,9 +68,49 @@ class BufferedImageData(
}
}
class BufferedImageDataWebGl(
override val width: Int,
override val height: Int,
val data: Uint8Array = Uint8Array(width * height * 4)
) : BufferedImage {
init {
for (x in 0 until width) {
for (y in 0 until height) {
val offset = (x * 4 + y * width * 4)
data[offset + 3] = 255.toShort().asDynamic()
}
}
}
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 = data
return (display[offset + 0].toInt() and 0xff) shl 16 +
(display[offset + 1].toInt() and 0xff) shl 8 +
(display[offset + 2].toInt() and 0xff) shl 0 + 255
}
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)
data[offset + 0] = ((intVal shr 16) and 0xff).toShort().asDynamic()
data[offset + 1] = ((intVal shr 8) and 0xff).toShort().asDynamic()
data[offset + 2] = ((intVal shr 0) and 0xff).toShort().asDynamic()
//data[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()));
}
actual fun createGLImage(width: Int, height: Int): BufferedImage {
return BufferedImageDataWebGl(width, height)
}

View File

@@ -1,24 +1,19 @@
package mtmc.util
import kotlinx.browser.window
import mtmc.display
import mtmc.mainView
import kotlin.js.Date
var lastMemoryUpdate = currentTimeMillis()
actual fun currentTimeMillis(): Long = Date().getTime().toLong()
actual fun currentTimeMillis(): Double = Date().getTime()
actual fun requestAnimationFrame(action: (Double) -> Unit) {
window.requestAnimationFrame {
action(it)
display.requestUpdate()
if (currentTimeMillis() - lastMemoryUpdate > 100) {
mainView.registerView.requestUpdate()
mainView.memoryView.requestUpdate()
lastMemoryUpdate = currentTimeMillis()
}
}
}
actual fun immediateTimeout(action: (Double) -> Unit): Int = window.setTimeout(action, 0)
actual fun time(): Double = window.performance.now()
actual fun setTimeout(action: () -> Unit) {
window.setTimeout(action)
}

View File

@@ -19,10 +19,14 @@ import org.w3c.dom.events.KeyboardEvent
class ConsoleView(
val computer: MonTanaMiniComputer
) : Komponent() {
val history: MutableList<String> = mutableListOf()
var input: String = ""
var output = StringBuilder()
private var inputElement: HTMLInputElement? = null
init {
output.append(computer.console.consumeLines())
}
override fun HtmlBuilder.render() {
div("console") {
onClickFunction = {
@@ -30,7 +34,7 @@ class ConsoleView(
}
div("console-history") {
div {
+computer.console.getOutput()
+output.toString()
}
}
div("console-input") {
@@ -41,6 +45,7 @@ class ConsoleView(
value = input
autoFocus = true
inputElement = currentElement() as? HTMLInputElement
currentElement().scrollIntoView()
window.setTimeout({
inputElement?.focus()
}, 0)
@@ -61,11 +66,17 @@ class ConsoleView(
}
private fun handleCommand() {
//history.add(input)
computer.console.print("mtmc$ ")
computer.console.println(input)
Shell.execCommand(input, computer)
input = ""
output.append(computer.console.consumeLines())
if (output.length > 1000 && output.contains("\n")) {
output = output.deleteRange(0, output.indexOf("\n"))
}
mainView.registerView.requestUpdate()
mainView.memoryView.requestUpdate()
display.requestUpdate()

View File

@@ -1,18 +1,156 @@
package mtmc.view
import kotlinx.html.InputType
import kotlinx.html.button
import kotlinx.html.div
import kotlinx.html.i
import kotlinx.html.input
import kotlinx.html.js.onChangeFunction
import kotlinx.html.js.onClickFunction
import kotlinx.html.label
import kotlinx.html.option
import kotlinx.html.select
import kotlinx.html.span
import mtmc.display
import mtmc.emulator.MonTanaMiniComputer
import mtmc.mainView
import mtmc.updateState
import nl.astraeus.komp.HtmlBuilder
import nl.astraeus.komp.Komponent
import org.w3c.dom.HTMLSelectElement
import kotlin.text.Typography.nbsp
class ControlView(
val computer: MonTanaMiniComputer
) : Komponent() {
override fun HtmlBuilder.render() {
div {
+"Controls view"
div("control-panel") {
div("control-header") {
span {
+"m"
}
span {
+"s"
}
span {
+"u"
}
nbsp
i {
+"MTMC-16"
}
}
div("control-secondary") {
+"MonTana Mini-Computer"
}
div("control-buttons") {
label {
+"Update:"
htmlFor = "update-state"
input {
type = InputType.checkBox
checked = updateState
onClickFunction = {
updateState = !updateState
requestUpdate()
}
}
}
label {
select {
name = "speed"
option {
value = "1"
+"1 Hz"
}
option {
value = "10"
+"10 Hz"
}
option {
value = "100"
+"100 Hz"
}
option {
value = "1000"
+"1 Khz"
}
option {
value = "1000000"
selected = true
+"1 Mhz"
}
option {
value = "2000000"
+"2 Mhz"
}
option {
value = "5000000"
+"5 Mhz"
}
option {
value = "10000000"
+"10 Mhz"
}
onChangeFunction = {
val target = it.target as? HTMLSelectElement
target?.value?.toIntOrNull()?.let { speed ->
computer.speed = speed
}
}
}
}
// Buttons with fx-* attributes
button {
if (computer.getStatus() != MonTanaMiniComputer.ComputerStatus.EXECUTING) {
+"run"
onClickFunction = {
computer.run()
requestUpdate()
}
} else {
+"pause"
onClickFunction = {
computer.pause()
requestUpdate()
}
}
}
button {
disabled =
computer.getStatus() == MonTanaMiniComputer.ComputerStatus.WAITING && computer.rewindIndex >= 0
+"back"
onClickFunction = {
computer.back()
mainView.requestUpdate()
display.requestUpdate()
}
}
button {
+"step"
onClickFunction = {
computer.step()
mainView.requestUpdate()
display.requestUpdate()
}
}
button {
+"reset"
onClickFunction = {
computer.initMemory()
mainView.requestUpdate()
display.requestUpdate()
}
}
}
}
}

View File

@@ -4,13 +4,43 @@ import kotlinx.html.canvas
import kotlinx.html.div
import mtmc.display
import mtmc.emulator.BufferedImageData
import mtmc.emulator.BufferedImageDataWebGl
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.khronos.webgl.Float32Array
import org.khronos.webgl.WebGLBuffer
import org.khronos.webgl.WebGLProgram
import org.khronos.webgl.WebGLRenderingContext
import org.khronos.webgl.WebGLShader
import org.khronos.webgl.WebGLTexture
import org.w3c.dom.HTMLCanvasElement
// language=GLSL
val vertexShader = """
attribute vec2 a_pos;
attribute vec2 a_uv;
varying vec2 v_uv;
void main() {
v_uv = a_uv;
gl_Position = vec4(a_pos, 0.0, 1.0);
}
"""
// language=GLSL
val fragmentShader = """
precision mediump float;
varying vec2 v_uv;
uniform sampler2D u_tex;
void main() {
gl_FragColor = texture2D(u_tex, v_uv);
}
""".trimIndent()
typealias GL = WebGLRenderingContext
class DiplayControlView(
val computer: MonTanaMiniComputer
) : Komponent() {
@@ -25,7 +55,10 @@ class DiplayControlView(
class DisplayView(
val computer: MonTanaMiniComputer
) : Komponent() {
var ctx: CanvasRenderingContext2D? = null
var ctx: WebGLRenderingContext? = null
var program: WebGLProgram? = null
var texture: WebGLTexture? = null
var buffer: WebGLBuffer? = null
override fun HtmlBuilder.render() {
canvas("display-canvas") {
@@ -34,18 +67,107 @@ class DisplayView(
val cv = currentElement() as? HTMLCanvasElement
ctx = cv?.getContext("2d")?.unsafeCast<CanvasRenderingContext2D>()
ctx = cv?.getContext("webgl")?.unsafeCast<WebGLRenderingContext>()
ctx?.fillStyle = "#400040"
ctx?.fillRect(0.0, 0.0, 160.0, 144.0)
if (program == null) {
createProgram()
createBuffer()
createTexture()
}
}
}
private fun createTexture() {
ctx?.let { gl: WebGLRenderingContext ->
texture = gl.createTexture()
gl.bindTexture(GL.TEXTURE_2D, texture)
// Set texture parameters for pixel-perfect rendering
gl.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_WRAP_S, GL.CLAMP_TO_EDGE)
gl.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_WRAP_T, GL.CLAMP_TO_EDGE)
gl.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_MIN_FILTER, GL.NEAREST)
gl.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_MAG_FILTER, GL.NEAREST)
}
}
private fun createBuffer() {
ctx?.let { gl ->
// Create quad vertices
val vertices = Float32Array(
arrayOf(
-1f, -1f, 0f, 1f, // position, texCoord
1f, -1f, 1f, 1f,
-1f, 1f, 0f, 0f,
1f, 1f, 1f, 0f
)
)
buffer = gl.createBuffer();
gl.bindBuffer(GL.ARRAY_BUFFER, buffer);
gl.bufferData(GL.ARRAY_BUFFER, vertices, GL.STATIC_DRAW);
}
}
private fun createProgram() {
val vs = createShader(WebGLRenderingContext.VERTEX_SHADER, vertexShader)
val fs = createShader(WebGLRenderingContext.FRAGMENT_SHADER, fragmentShader)
val prog = ctx?.createProgram()
if (vs != null && fs != null && prog != null) {
ctx?.attachShader(prog, vs)
ctx?.attachShader(prog, fs)
ctx?.linkProgram(prog)
}
program = prog
}
private fun createShader(type: Int, source: String): WebGLShader? {
var result: WebGLShader? = null
ctx?.let { gl ->
result = gl.createShader(type)
result?.let { shader ->
gl.shaderSource(shader, source)
gl.compileShader(shader)
}
}
return result
}
override fun renderUpdate() {
// move data to canvas
val buffer = computer.display.buffer
if (buffer is BufferedImageData) {
ctx?.putImageData(buffer.imageData, 0.0, 0.0)
//ctx?.putImageData(buffer.imageData, 0.0, 0.0)
} else if (buffer is BufferedImageDataWebGl) {
ctx?.let { gl ->
gl.clear(GL.COLOR_BUFFER_BIT)
gl.useProgram(program)
val positionLocation = gl.getAttribLocation(program, "a_pos")
val texCoordLocation = gl.getAttribLocation(program, "a_uv")
gl.bindBuffer(GL.ARRAY_BUFFER, this.buffer)
gl.enableVertexAttribArray(positionLocation)
gl.vertexAttribPointer(positionLocation, 2, GL.FLOAT, false, 16, 0)
gl.enableVertexAttribArray(texCoordLocation)
gl.vertexAttribPointer(texCoordLocation, 2, GL.FLOAT, false, 16, 8)
gl.bindTexture(GL.TEXTURE_2D, texture);
gl.texImage2D(
GL.TEXTURE_2D,
0, // level
GL.RGBA, // internal format
buffer.width,
buffer.height,
0, // border
GL.RGBA, // format
GL.UNSIGNED_BYTE, // type
buffer.data // data
)
// Draw quad
gl.drawArrays(GL.TRIANGLE_STRIP, 0, 4);
}
}
}
}

View File

@@ -89,7 +89,6 @@ class MemoryView(
else -> +"?"
}
}
}
}
}

View File

@@ -8,6 +8,7 @@ import kotlinx.html.classes
import kotlinx.html.div
import kotlinx.html.hr
import kotlinx.html.id
import kotlinx.html.span
import kotlinx.html.table
import kotlinx.html.td
import kotlinx.html.tr
@@ -36,9 +37,34 @@ class RegisterView(
showRegister(16)
showRegister(17)
tr {
td {
td("flags") {
colSpan = "3"
+"Flags"
span {
+"flags t:"
div("blinken") {
id = "flags-t"
if (!computer.isFlagTestBitSet) {
classes += "off"
}
}
}
span {
+"o:"
div("blinken") {
id = "flags-o"
classes += "off"
}
}
span {
+"e:"
div("blinken") {
id = "flags-e"
if (computer.getStatus() != MonTanaMiniComputer.ComputerStatus.PERMANENT_ERROR) {
classes += "off"
}
}
}
}
}
}

View File

@@ -1,3 +1,12 @@
:root {
--pdp-blue: #243571;
--pdp-light-blue: #3286ce;
--pdp-beige: #fdfddc;
--pdp-white: #f1f1f6;
--pdp-off-white: #F0EBCD;
--filetree-gray: #666;
}
html, body {
padding: 0;
margin: 0;
@@ -14,7 +23,7 @@ body {
grid-template-rows: 1fr;
overflow-y: auto;
gap: 5px;
background-color: #ccc;
background-color: var(--pdp-white);
}
.left-column {
@@ -52,6 +61,53 @@ body {
text-align: right;
}
/* control */
.control-panel > * {
margin: 4px;
}
.control-header {
background-color: var(--pdp-blue);
color: var(--pdp-white);
font-family: monospace;
font-size: 32px;
padding: 4px 24px;
margin-left: 0;
margin-right: 0;
}
.control-header span {
display: inline-block;
margin-top: -6px;
margin-bottom: -6px;
padding: 4px 8px;
font-weight: bold;
font-size: 38px;
border-right: 4px solid white;
border-left: 4px solid white;
}
.control-header span:not(:first-child) {
border-left: none;
}
.control-secondary {
background-color: var(--pdp-light-blue);
color: var(--pdp-white);
font-family: monospace;
font-size: 18px;
font-style: italic;
text-align: center;
padding: 4px 4px;
margin-left: 0;
margin-right: 0;
}
.control-buttons {
text-align: right;
}
/* registers */
table.register-table {
@@ -72,6 +128,20 @@ table.register-table tr td.register-lights {
padding-left: 20px;
}
table.register-table tr td.flags {
text-align: center;
}
table.register-table tr td.flags span {
margin-left: 8px;
}
table.register-table tr td.flags span .blinken {
margin-left: 4px;
top: 2px;
position: relative;
}
.blinken {
display: inline-block;
margin-right: 2px;
@@ -158,10 +228,11 @@ table.register-table tr td.register-lights {
font-family: monospace;
font-weight: bold;
padding: 5px;
overflow: auto;
}
.console-prompt {
margin-right: 10px;
margin-right: 6px;
}
input.console-input {
@@ -169,6 +240,8 @@ input.console-input {
background-color: #35291c;
border: none;
outline: none;
font-weight: bold;
font-family: monospace;
}
.small-button {

View File

@@ -6,4 +6,8 @@ actual fun createBufferedImage(width: Int, height: Int): BufferedImage {
actual fun createCanvasImage(width: Int, height: Int): BufferedImage {
TODO("Not yet implemented")
}
actual fun createGLImage(width: Int, height: Int): BufferedImage {
TODO("Not yet implemented")
}

View File

@@ -1,8 +1,12 @@
package mtmc.util
actual fun currentTimeMillis(): Long = System.currentTimeMillis()
actual fun currentTimeMillis(): Double = System.currentTimeMillis().toDouble()
actual fun requestAnimationFrame(action: (Double) -> Unit) {
error("requestAnimationFrame is not supported on JVM")
}
actual fun immediateTimeout(action: (Double) -> Unit): Int = 0
actual fun immediateTimeout(action: (Double) -> Unit): Int = 0
actual fun time(): Double = System.nanoTime() * 1000.0
actual fun setTimeout(action: () -> Unit) {}

View File

@@ -5,12 +5,13 @@ import io.undertow.server.HttpServerExchange
import io.undertow.server.handlers.PathHandler
import io.undertow.server.handlers.resource.PathResourceManager
import io.undertow.server.handlers.resource.ResourceHandler
import io.undertow.util.Headers
import mtmc.itemUrl
import java.nio.file.Paths
import kotlin.text.startsWith
object IndexHandler : HttpHandler {
override fun handleRequest(exchange: HttpServerExchange) {
exchange.responseHeaders.put(Headers.CONTENT_TYPE, "text/html")
if (exchange.requestPath.startsWith("/$itemUrl/")) {
exchange.responseSender.send(generateIndex(null))
} else {