generated from rnentjes/kotlin-server-web-undertow
Compare commits
5 Commits
37691dc7fa
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 4dcfe3a6ff | |||
| f14f316e38 | |||
| 11b069ddc5 | |||
| d7e331728f | |||
| 8457d3a854 |
13
LICENSE.txt
Normal file
13
LICENSE.txt
Normal 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.
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
@file:OptIn(ExperimentalDistributionDsl::class)
|
@file:OptIn(ExperimentalDistributionDsl::class)
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
|
||||||
import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalDistributionDsl
|
import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalDistributionDsl
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
@@ -21,7 +22,15 @@ repositories {
|
|||||||
|
|
||||||
kotlin {
|
kotlin {
|
||||||
jvmToolchain(21)
|
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 {
|
js {
|
||||||
binaries.executable()
|
binaries.executable()
|
||||||
browser {
|
browser {
|
||||||
@@ -40,9 +49,6 @@ kotlin {
|
|||||||
val commonMain by getting {
|
val commonMain by getting {
|
||||||
dependencies {
|
dependencies {
|
||||||
api("nl.astraeus:kotlin-simple-logging:1.1.1")
|
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
|
val commonTest by getting
|
||||||
@@ -72,4 +78,4 @@ kotlin {
|
|||||||
}
|
}
|
||||||
val jsTest by getting
|
val jsTest by getting
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,6 +1,6 @@
|
|||||||
#Sun Apr 28 09:54:33 CEST 2024
|
#Sun Apr 28 09:54:33 CEST 2024
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
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
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ expect fun createBufferedImage(width: Int, height: Int): BufferedImage
|
|||||||
|
|
||||||
expect fun createCanvasImage(width: Int, height: Int): BufferedImage
|
expect fun createCanvasImage(width: Int, height: Int): BufferedImage
|
||||||
|
|
||||||
|
expect fun createGLImage(width: Int, height: Int): BufferedImage
|
||||||
|
|
||||||
interface BufferedImage {
|
interface BufferedImage {
|
||||||
val width: Int
|
val width: Int
|
||||||
val height: Int
|
val height: Int
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ package mtmc.emulator
|
|||||||
import mtmc.emulator.MonTanaMiniComputer.ComputerStatus
|
import mtmc.emulator.MonTanaMiniComputer.ComputerStatus
|
||||||
import mtmc.util.currentTimeMillis
|
import mtmc.util.currentTimeMillis
|
||||||
import mtmc.util.requestAnimationFrame
|
import mtmc.util.requestAnimationFrame
|
||||||
|
import mtmc.util.setTimeout
|
||||||
|
import mtmc.util.time
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
|
|
||||||
@@ -27,21 +29,22 @@ class MTMCClock(
|
|||||||
var frame = 0
|
var frame = 0
|
||||||
|
|
||||||
fun run() {
|
fun run() {
|
||||||
requestAnimationFrame { handleFrame(it) }
|
requestAnimationFrame { handleFrame() }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun handleFrame(time: Double) {
|
fun handleFrame() {
|
||||||
// figure out how many instructions to execute this 'time' duration
|
// figure out how many instructions to execute this 'time' duration
|
||||||
// maximize time so we don't hang if the emulator is too slow
|
// maximize time so we don't hang if the emulator is too slow
|
||||||
|
val time = time()
|
||||||
if (lastFrame == 0.0) {
|
if (lastFrame == 0.0) {
|
||||||
lastFrame = time
|
lastFrame = time
|
||||||
requestAnimationFrame { handleFrame(it) }
|
requestAnimationFrame { handleFrame() }
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
val delta = time - lastFrame
|
val delta = time - lastFrame
|
||||||
lastFrame = time
|
lastFrame = time
|
||||||
|
|
||||||
val actualTime = min(delta / 1000.0, 0.05)
|
val timeToProcess = min(delta, 16.0)
|
||||||
|
|
||||||
// assume 1Hz = 1 instruction/second
|
// assume 1Hz = 1 instruction/second
|
||||||
if (computer.getStatus() == ComputerStatus.EXECUTING) {
|
if (computer.getStatus() == ComputerStatus.EXECUTING) {
|
||||||
@@ -50,22 +53,28 @@ class MTMCClock(
|
|||||||
speed = 1L
|
speed = 1L
|
||||||
}
|
}
|
||||||
|
|
||||||
instructionsToRun += actualTime * speed
|
instructionsToRun += timeToProcess * speed / 1000.0
|
||||||
val pulse: Long = instructionsToRun.toLong()
|
val pulse: Long = instructionsToRun.toLong() + 1
|
||||||
instructionsToRun -= pulse
|
instructionsToRun -= pulse
|
||||||
|
|
||||||
val time = currentTimeMillis()
|
val start = time()
|
||||||
val ir = computer.pulse(pulse)
|
val ir = computer.pulse(pulse)
|
||||||
instructions += ir
|
instructions += ir
|
||||||
|
val duration = (time() - start)
|
||||||
|
val actual = ir / (delta / 1000.0)
|
||||||
if (frame % 100 == 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
|
virtual += instructions
|
||||||
ips = (instructions / actualTime).toLong()
|
ips = (instructions / timeToProcess).toLong()
|
||||||
frame++
|
frame++
|
||||||
|
|
||||||
requestAnimationFrame { handleFrame(it) }
|
if (duration > timeToProcess) {
|
||||||
|
setTimeout({ handleFrame() })
|
||||||
|
} else {
|
||||||
|
requestAnimationFrame { handleFrame() }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//println("Executed " + instructions + " instructions at a rate of " + ips + " ips (speed = " + speed + ")")
|
//println("Executed " + instructions + " instructions at a rate of " + ips + " ips (speed = " + speed + ")")
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ class MTMCConsole(private val computer: MonTanaMiniComputer) {
|
|||||||
var sysConsole: Console? = null
|
var sysConsole: Console? = null
|
||||||
|
|
||||||
// non-interactive data
|
// non-interactive data
|
||||||
private val output = StringBuilder()
|
private var output = StringBuilder()
|
||||||
private var shortValueSet = false
|
private var shortValueSet = false
|
||||||
private var shortValue: Short = 0
|
private var shortValue: Short = 0
|
||||||
private var stringValue: String? = null
|
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 ""
|
val text = if (index >= 0) output.substring(0, index + 1) else ""
|
||||||
|
|
||||||
if (index >= 0) {
|
if (index >= 0) {
|
||||||
output.removeRange(0, index + 1)
|
val updated = StringBuilder()
|
||||||
|
updated.append(output.removeRange(0, index + 1))
|
||||||
|
output = updated
|
||||||
}
|
}
|
||||||
|
|
||||||
return text
|
return text
|
||||||
@@ -116,7 +118,7 @@ class MTMCConsole(private val computer: MonTanaMiniComputer) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun resetOutput() {
|
fun resetOutput() {
|
||||||
output.removeRange(0, output.length)
|
output.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class Mode {
|
enum class Mode {
|
||||||
|
|||||||
@@ -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) {
|
||||||
val buffer: BufferedImage = createCanvasImage(COLS, ROWS)
|
val buffer: BufferedImage = createGLImage(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)
|
||||||
@@ -18,13 +18,8 @@ class MTMCDisplay(private val computer: MonTanaMiniComputer) {
|
|||||||
LIGHT(87, 124, 68),
|
LIGHT(87, 124, 68),
|
||||||
LIGHTEST(127, 134, 15);
|
LIGHTEST(127, 134, 15);
|
||||||
|
|
||||||
val intVal: Int
|
val intVal: Int = 0xFF shl 24 or (r shl 16) or (g shl 8) or b
|
||||||
val javaColor: Color
|
val javaColor: Color = Color(r, g, b)
|
||||||
|
|
||||||
init {
|
|
||||||
this.intVal = 0xFF shl 24 or (r shl 16) or (g shl 8) or b
|
|
||||||
javaColor = Color(r, g, b)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun distance(r: Int, g: Int, b: Int): Int {
|
fun distance(r: Int, g: Int, b: Int): Int {
|
||||||
val dr = this.r - r
|
val dr = this.r - r
|
||||||
@@ -56,15 +51,20 @@ class MTMCDisplay(private val computer: MonTanaMiniComputer) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun loadSplashScreen() {
|
private fun loadSplashScreen() {
|
||||||
/* try {
|
currentColor = DisplayColor.DARK
|
||||||
val bytes: ByteArray = Base64.getDecoder().decode(SPLASH_SCREEN)
|
var currentColorCount = 0
|
||||||
val bais = ByteArrayInputStream(bytes)
|
var currentColor = 0
|
||||||
var img: BufferedImage? = null
|
var colorIndex = 0
|
||||||
img = ImageIO.read(bais)
|
for (col in 0..<COLS) {
|
||||||
loadScaledImage(img)
|
for (row in 0..<ROWS) {
|
||||||
} catch (e: IOException) {
|
if (currentColorCount == 0) {
|
||||||
e.printStackTrace()
|
currentColorCount = SPLASH_SCREEN_COLORS[colorIndex++]
|
||||||
}*/
|
currentColor = SPLASH_SCREEN_COLORS[colorIndex++]
|
||||||
|
}
|
||||||
|
setPixel(col, row, DisplayColor.entries[currentColor])
|
||||||
|
currentColorCount--
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadImage(data: ByteArray): BufferedImage? {
|
private fun loadImage(data: ByteArray): BufferedImage? {
|
||||||
@@ -107,7 +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)
|
try {
|
||||||
|
buffer.setRGB(col, row, color.intVal)
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
throw e
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getPixel(col: Int, row: Int): Short {
|
fun getPixel(col: Int, row: Int): Short {
|
||||||
@@ -173,7 +177,7 @@ class MTMCDisplay(private val computer: MonTanaMiniComputer) {
|
|||||||
//computer.notifyOfDisplayUpdate()
|
//computer.notifyOfDisplayUpdate()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun toPng(): ByteArray? {
|
fun toPng(): ByteArray {
|
||||||
return byteArray
|
return byteArray
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ class MonTanaMiniComputer {
|
|||||||
var clock: MTMCClock = MTMCClock(this)
|
var clock: MTMCClock = MTMCClock(this)
|
||||||
var fileSystem: FileSystem = FileSystem(this)
|
var fileSystem: FileSystem = FileSystem(this)
|
||||||
|
|
||||||
var rewindSteps = Array<RewindStep?>(MAX_REWIND_STEPS) { null }
|
var rewindSteps = Array(MAX_REWIND_STEPS) { RewindStep() }
|
||||||
var rewindIndex = -1
|
var rewindIndex = -1
|
||||||
|
|
||||||
// listeners
|
// listeners
|
||||||
@@ -48,7 +48,6 @@ 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)
|
||||||
rewindIndex = -1
|
|
||||||
setRegisterValue(
|
setRegisterValue(
|
||||||
Register.SP,
|
Register.SP,
|
||||||
MEMORY_SIZE.toShort().toInt()
|
MEMORY_SIZE.toShort().toInt()
|
||||||
@@ -134,11 +133,9 @@ class MonTanaMiniComputer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun fetchAndExecute() {
|
fun fetchAndExecute() {
|
||||||
currentRewindStep = RewindStep()
|
rewindIndex = (rewindIndex + 1) % rewindSteps.size
|
||||||
currentRewindStep?.let {
|
rewindSteps[rewindIndex].index = 0
|
||||||
rewindIndex = (rewindIndex + 1) % rewindSteps.size
|
|
||||||
rewindSteps.set(rewindIndex, it)
|
|
||||||
}
|
|
||||||
fetchCurrentInstruction()
|
fetchCurrentInstruction()
|
||||||
val instruction = getRegisterValue(Register.IR)
|
val instruction = getRegisterValue(Register.IR)
|
||||||
if (isDoubleWordInstruction(instruction)) {
|
if (isDoubleWordInstruction(instruction)) {
|
||||||
@@ -792,7 +789,7 @@ class MonTanaMiniComputer {
|
|||||||
val currentValue = memory[address]
|
val currentValue = memory[address]
|
||||||
addRewindStep { memory[address] = currentValue }
|
addRewindStep { memory[address] = currentValue }
|
||||||
memory[address] = value
|
memory[address] = value
|
||||||
observers!!.forEach { o: MTMCObserver? ->
|
observers.forEach { o: MTMCObserver? ->
|
||||||
o!!.memoryUpdated(address, value)
|
o!!.memoryUpdated(address, value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -816,7 +813,7 @@ class MonTanaMiniComputer {
|
|||||||
registerFile[register] = currentValue
|
registerFile[register] = currentValue
|
||||||
}
|
}
|
||||||
registerFile[register] = value.toShort()
|
registerFile[register] = value.toShort()
|
||||||
observers!!.forEach { o: MTMCObserver? ->
|
observers.forEach { o: MTMCObserver? ->
|
||||||
o!!.registerUpdated(register, value)
|
o!!.registerUpdated(register, value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,13 +3,16 @@ package mtmc.emulator
|
|||||||
import mtmc.util.Runnable
|
import mtmc.util.Runnable
|
||||||
|
|
||||||
class RewindStep {
|
class RewindStep {
|
||||||
var subSteps: MutableList<Runnable?> = mutableListOf()
|
var subSteps: Array<Runnable> = Array(10) { {} }
|
||||||
|
var index = 0
|
||||||
|
|
||||||
fun rewind() {
|
fun rewind() {
|
||||||
subSteps.reversed().forEach({ obj: Runnable? -> obj!!.invoke() })
|
subSteps.reversed().forEach({ obj: Runnable? -> obj!!.invoke() })
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addSubStep(subStep: Runnable?) {
|
fun addSubStep(subStep: Runnable?) {
|
||||||
subSteps.add(subStep)
|
if (subStep != null && index < subSteps.size) {
|
||||||
|
subSteps[index++] = subStep
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
1727
src/commonMain/kotlin/mtmc/emulator/SplashData.kt
Normal file
1727
src/commonMain/kotlin/mtmc/emulator/SplashData.kt
Normal file
File diff suppressed because it is too large
Load Diff
@@ -12,7 +12,7 @@ import kotlin.math.min
|
|||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
|
|
||||||
class MTOS(private val computer: MonTanaMiniComputer) {
|
class MTOS(private val computer: MonTanaMiniComputer) {
|
||||||
private var timer: Long = 0
|
private var timer: Double = 0.0
|
||||||
var random: Random = Random.Default
|
var random: Random = Random.Default
|
||||||
|
|
||||||
// Editor support
|
// Editor support
|
||||||
@@ -340,7 +340,7 @@ class MTOS(private val computer: MonTanaMiniComputer) {
|
|||||||
|
|
||||||
computer.setRegisterValue(
|
computer.setRegisterValue(
|
||||||
Register.RV,
|
Register.RV,
|
||||||
max(0, this.timer - currentTimeMillis()).toInt()
|
max(0.0, this.timer - currentTimeMillis()).toInt()
|
||||||
)
|
)
|
||||||
} else if (syscallNumber == getValue("drawimg").toShort()) {
|
} else if (syscallNumber == getValue("drawimg").toShort()) {
|
||||||
val image = computer.getRegisterValue(Register.A0)
|
val image = computer.getRegisterValue(Register.A0)
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
package mtmc.util
|
package mtmc.util
|
||||||
|
|
||||||
expect fun currentTimeMillis(): Long
|
expect fun currentTimeMillis(): Double
|
||||||
|
|
||||||
|
expect fun time(): Double
|
||||||
|
|
||||||
expect fun requestAnimationFrame(action: (Double) -> Unit)
|
expect fun requestAnimationFrame(action: (Double) -> Unit)
|
||||||
|
|
||||||
|
expect fun setTimeout(action: () -> Unit)
|
||||||
|
|
||||||
expect fun immediateTimeout(action: (Double) -> Unit): Int
|
expect fun immediateTimeout(action: (Double) -> Unit): Int
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
package mtmc
|
package mtmc
|
||||||
|
|
||||||
import kotlinx.browser.document
|
import kotlinx.browser.document
|
||||||
|
import kotlinx.browser.window
|
||||||
import mtmc.emulator.MonTanaMiniComputer
|
import mtmc.emulator.MonTanaMiniComputer
|
||||||
|
import mtmc.util.currentTimeMillis
|
||||||
import mtmc.view.DisplayView
|
import mtmc.view.DisplayView
|
||||||
import mtmc.view.MTMCView
|
import mtmc.view.MTMCView
|
||||||
import nl.astraeus.komp.Komponent
|
import nl.astraeus.komp.Komponent
|
||||||
@@ -11,12 +13,31 @@ val mainView = MTMCView(computer)
|
|||||||
val display = DisplayView(computer)
|
val display = DisplayView(computer)
|
||||||
|
|
||||||
fun main() {
|
fun main() {
|
||||||
computer.speed = 2000000 // default to 1hz
|
computer.speed = 1000000
|
||||||
computer.load(lifeCode, lifeData)
|
computer.load(lifeCode, lifeData)
|
||||||
|
//computer.load(snakeCode, snakeData)
|
||||||
|
|
||||||
Komponent.create(document.body!!, mainView)
|
Komponent.create(document.body!!, mainView)
|
||||||
|
|
||||||
computer.start()
|
computer.start()
|
||||||
|
|
||||||
mainView.requestUpdate()
|
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() }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package mtmc.emulator
|
package mtmc.emulator
|
||||||
|
|
||||||
import kotlinx.browser.document
|
import kotlinx.browser.document
|
||||||
|
import org.khronos.webgl.Uint8Array
|
||||||
import org.khronos.webgl.get
|
import org.khronos.webgl.get
|
||||||
import org.khronos.webgl.set
|
import org.khronos.webgl.set
|
||||||
import org.w3c.dom.CanvasRenderingContext2D
|
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 canvas = document.createElement("canvas") as HTMLCanvasElement
|
||||||
val ctx = canvas.getContext("2d") as CanvasRenderingContext2D
|
val ctx = canvas.getContext("2d") as CanvasRenderingContext2D
|
||||||
|
|
||||||
actual fun createCanvasImage(width: Int, height: Int): BufferedImage {
|
actual fun createCanvasImage(width: Int, height: Int): BufferedImage {
|
||||||
return BufferedImageData(ctx.createImageData(width.toDouble(), height.toDouble()));
|
return BufferedImageData(ctx.createImageData(width.toDouble(), height.toDouble()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
actual fun createGLImage(width: Int, height: Int): BufferedImage {
|
||||||
|
return BufferedImageDataWebGl(width, height)
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,24 +1,19 @@
|
|||||||
package mtmc.util
|
package mtmc.util
|
||||||
|
|
||||||
import kotlinx.browser.window
|
import kotlinx.browser.window
|
||||||
import mtmc.display
|
|
||||||
import mtmc.mainView
|
|
||||||
import kotlin.js.Date
|
import kotlin.js.Date
|
||||||
|
|
||||||
var lastMemoryUpdate = currentTimeMillis()
|
actual fun currentTimeMillis(): Double = Date().getTime()
|
||||||
|
|
||||||
actual fun currentTimeMillis(): Long = Date().getTime().toLong()
|
|
||||||
actual fun requestAnimationFrame(action: (Double) -> Unit) {
|
actual fun requestAnimationFrame(action: (Double) -> Unit) {
|
||||||
window.requestAnimationFrame {
|
window.requestAnimationFrame {
|
||||||
action(it)
|
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 immediateTimeout(action: (Double) -> Unit): Int = window.setTimeout(action, 0)
|
||||||
|
|
||||||
|
actual fun time(): Double = window.performance.now()
|
||||||
|
|
||||||
|
actual fun setTimeout(action: () -> Unit) {
|
||||||
|
window.setTimeout(action)
|
||||||
|
}
|
||||||
@@ -19,10 +19,14 @@ import org.w3c.dom.events.KeyboardEvent
|
|||||||
class ConsoleView(
|
class ConsoleView(
|
||||||
val computer: MonTanaMiniComputer
|
val computer: MonTanaMiniComputer
|
||||||
) : Komponent() {
|
) : Komponent() {
|
||||||
val history: MutableList<String> = mutableListOf()
|
|
||||||
var input: String = ""
|
var input: String = ""
|
||||||
|
var output = StringBuilder()
|
||||||
private var inputElement: HTMLInputElement? = null
|
private var inputElement: HTMLInputElement? = null
|
||||||
|
|
||||||
|
init {
|
||||||
|
output.append(computer.console.consumeLines())
|
||||||
|
}
|
||||||
|
|
||||||
override fun HtmlBuilder.render() {
|
override fun HtmlBuilder.render() {
|
||||||
div("console") {
|
div("console") {
|
||||||
onClickFunction = {
|
onClickFunction = {
|
||||||
@@ -30,7 +34,7 @@ class ConsoleView(
|
|||||||
}
|
}
|
||||||
div("console-history") {
|
div("console-history") {
|
||||||
div {
|
div {
|
||||||
+computer.console.getOutput()
|
+output.toString()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
div("console-input") {
|
div("console-input") {
|
||||||
@@ -41,6 +45,7 @@ class ConsoleView(
|
|||||||
value = input
|
value = input
|
||||||
autoFocus = true
|
autoFocus = true
|
||||||
inputElement = currentElement() as? HTMLInputElement
|
inputElement = currentElement() as? HTMLInputElement
|
||||||
|
currentElement().scrollIntoView()
|
||||||
window.setTimeout({
|
window.setTimeout({
|
||||||
inputElement?.focus()
|
inputElement?.focus()
|
||||||
}, 0)
|
}, 0)
|
||||||
@@ -61,11 +66,17 @@ class ConsoleView(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun handleCommand() {
|
private fun handleCommand() {
|
||||||
//history.add(input)
|
computer.console.print("mtmc$ ")
|
||||||
|
computer.console.println(input)
|
||||||
|
|
||||||
Shell.execCommand(input, computer)
|
Shell.execCommand(input, computer)
|
||||||
|
|
||||||
input = ""
|
input = ""
|
||||||
|
output.append(computer.console.consumeLines())
|
||||||
|
if (output.length > 1000 && output.contains("\n")) {
|
||||||
|
output = output.deleteRange(0, output.indexOf("\n"))
|
||||||
|
}
|
||||||
|
|
||||||
mainView.registerView.requestUpdate()
|
mainView.registerView.requestUpdate()
|
||||||
mainView.memoryView.requestUpdate()
|
mainView.memoryView.requestUpdate()
|
||||||
display.requestUpdate()
|
display.requestUpdate()
|
||||||
|
|||||||
@@ -1,18 +1,156 @@
|
|||||||
package mtmc.view
|
package mtmc.view
|
||||||
|
|
||||||
|
import kotlinx.html.InputType
|
||||||
|
import kotlinx.html.button
|
||||||
import kotlinx.html.div
|
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.emulator.MonTanaMiniComputer
|
||||||
|
import mtmc.mainView
|
||||||
|
import mtmc.updateState
|
||||||
import nl.astraeus.komp.HtmlBuilder
|
import nl.astraeus.komp.HtmlBuilder
|
||||||
import nl.astraeus.komp.Komponent
|
import nl.astraeus.komp.Komponent
|
||||||
|
import org.w3c.dom.HTMLSelectElement
|
||||||
|
import kotlin.text.Typography.nbsp
|
||||||
|
|
||||||
class ControlView(
|
class ControlView(
|
||||||
val computer: MonTanaMiniComputer
|
val computer: MonTanaMiniComputer
|
||||||
) : Komponent() {
|
) : Komponent() {
|
||||||
|
|
||||||
override fun HtmlBuilder.render() {
|
override fun HtmlBuilder.render() {
|
||||||
div {
|
div("control-panel") {
|
||||||
+"Controls view"
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,13 +4,43 @@ 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.BufferedImageData
|
||||||
|
import mtmc.emulator.BufferedImageDataWebGl
|
||||||
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.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
|
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(
|
class DiplayControlView(
|
||||||
val computer: MonTanaMiniComputer
|
val computer: MonTanaMiniComputer
|
||||||
) : Komponent() {
|
) : Komponent() {
|
||||||
@@ -25,7 +55,10 @@ class DiplayControlView(
|
|||||||
class DisplayView(
|
class DisplayView(
|
||||||
val computer: MonTanaMiniComputer
|
val computer: MonTanaMiniComputer
|
||||||
) : Komponent() {
|
) : 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() {
|
override fun HtmlBuilder.render() {
|
||||||
canvas("display-canvas") {
|
canvas("display-canvas") {
|
||||||
@@ -34,18 +67,107 @@ class DisplayView(
|
|||||||
|
|
||||||
val cv = currentElement() as? HTMLCanvasElement
|
val cv = currentElement() as? HTMLCanvasElement
|
||||||
|
|
||||||
ctx = cv?.getContext("2d")?.unsafeCast<CanvasRenderingContext2D>()
|
ctx = cv?.getContext("webgl")?.unsafeCast<WebGLRenderingContext>()
|
||||||
|
|
||||||
ctx?.fillStyle = "#400040"
|
if (program == null) {
|
||||||
ctx?.fillRect(0.0, 0.0, 160.0, 144.0)
|
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() {
|
override fun renderUpdate() {
|
||||||
// move data to canvas
|
// move data to canvas
|
||||||
val buffer = computer.display.buffer
|
val buffer = computer.display.buffer
|
||||||
if (buffer is BufferedImageData) {
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -89,7 +89,6 @@ class MemoryView(
|
|||||||
else -> +"?"
|
else -> +"?"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import kotlinx.html.classes
|
|||||||
import kotlinx.html.div
|
import kotlinx.html.div
|
||||||
import kotlinx.html.hr
|
import kotlinx.html.hr
|
||||||
import kotlinx.html.id
|
import kotlinx.html.id
|
||||||
|
import kotlinx.html.span
|
||||||
import kotlinx.html.table
|
import kotlinx.html.table
|
||||||
import kotlinx.html.td
|
import kotlinx.html.td
|
||||||
import kotlinx.html.tr
|
import kotlinx.html.tr
|
||||||
@@ -36,9 +37,34 @@ class RegisterView(
|
|||||||
showRegister(16)
|
showRegister(16)
|
||||||
showRegister(17)
|
showRegister(17)
|
||||||
tr {
|
tr {
|
||||||
td {
|
td("flags") {
|
||||||
colSpan = "3"
|
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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
html, body {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@@ -14,7 +23,7 @@ body {
|
|||||||
grid-template-rows: 1fr;
|
grid-template-rows: 1fr;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
gap: 5px;
|
gap: 5px;
|
||||||
background-color: #ccc;
|
background-color: var(--pdp-white);
|
||||||
}
|
}
|
||||||
|
|
||||||
.left-column {
|
.left-column {
|
||||||
@@ -52,6 +61,53 @@ body {
|
|||||||
text-align: right;
|
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 */
|
/* registers */
|
||||||
|
|
||||||
table.register-table {
|
table.register-table {
|
||||||
@@ -72,6 +128,20 @@ table.register-table tr td.register-lights {
|
|||||||
padding-left: 20px;
|
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 {
|
.blinken {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin-right: 2px;
|
margin-right: 2px;
|
||||||
@@ -158,10 +228,11 @@ table.register-table tr td.register-lights {
|
|||||||
font-family: monospace;
|
font-family: monospace;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.console-prompt {
|
.console-prompt {
|
||||||
margin-right: 10px;
|
margin-right: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
input.console-input {
|
input.console-input {
|
||||||
@@ -169,6 +240,8 @@ input.console-input {
|
|||||||
background-color: #35291c;
|
background-color: #35291c;
|
||||||
border: none;
|
border: none;
|
||||||
outline: none;
|
outline: none;
|
||||||
|
font-weight: bold;
|
||||||
|
font-family: monospace;
|
||||||
}
|
}
|
||||||
|
|
||||||
.small-button {
|
.small-button {
|
||||||
|
|||||||
@@ -6,4 +6,8 @@ actual fun createBufferedImage(width: Int, height: Int): BufferedImage {
|
|||||||
|
|
||||||
actual fun createCanvasImage(width: Int, height: Int): BufferedImage {
|
actual fun createCanvasImage(width: Int, height: Int): BufferedImage {
|
||||||
TODO("Not yet implemented")
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
actual fun createGLImage(width: Int, height: Int): BufferedImage {
|
||||||
|
TODO("Not yet implemented")
|
||||||
}
|
}
|
||||||
@@ -1,8 +1,12 @@
|
|||||||
package mtmc.util
|
package mtmc.util
|
||||||
|
|
||||||
actual fun currentTimeMillis(): Long = System.currentTimeMillis()
|
actual fun currentTimeMillis(): Double = System.currentTimeMillis().toDouble()
|
||||||
actual fun requestAnimationFrame(action: (Double) -> Unit) {
|
actual fun requestAnimationFrame(action: (Double) -> Unit) {
|
||||||
error("requestAnimationFrame is not supported on JVM")
|
error("requestAnimationFrame is not supported on JVM")
|
||||||
}
|
}
|
||||||
|
|
||||||
actual fun immediateTimeout(action: (Double) -> Unit): Int = 0
|
actual fun immediateTimeout(action: (Double) -> Unit): Int = 0
|
||||||
|
|
||||||
|
actual fun time(): Double = System.nanoTime() * 1000.0
|
||||||
|
|
||||||
|
actual fun setTimeout(action: () -> Unit) {}
|
||||||
@@ -5,12 +5,13 @@ import io.undertow.server.HttpServerExchange
|
|||||||
import io.undertow.server.handlers.PathHandler
|
import io.undertow.server.handlers.PathHandler
|
||||||
import io.undertow.server.handlers.resource.PathResourceManager
|
import io.undertow.server.handlers.resource.PathResourceManager
|
||||||
import io.undertow.server.handlers.resource.ResourceHandler
|
import io.undertow.server.handlers.resource.ResourceHandler
|
||||||
|
import io.undertow.util.Headers
|
||||||
import mtmc.itemUrl
|
import mtmc.itemUrl
|
||||||
import java.nio.file.Paths
|
import java.nio.file.Paths
|
||||||
import kotlin.text.startsWith
|
|
||||||
|
|
||||||
object IndexHandler : HttpHandler {
|
object IndexHandler : HttpHandler {
|
||||||
override fun handleRequest(exchange: HttpServerExchange) {
|
override fun handleRequest(exchange: HttpServerExchange) {
|
||||||
|
exchange.responseHeaders.put(Headers.CONTENT_TYPE, "text/html")
|
||||||
if (exchange.requestPath.startsWith("/$itemUrl/")) {
|
if (exchange.requestPath.startsWith("/$itemUrl/")) {
|
||||||
exchange.responseSender.send(generateIndex(null))
|
exchange.responseSender.send(generateIndex(null))
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
Reference in New Issue
Block a user