From 737f4fd8674c9f45087f164405cd2c1acc23440f Mon Sep 17 00:00:00 2001 From: Ponali Date: Wed, 13 Aug 2025 18:12:33 +0200 Subject: [PATCH 1/6] Fix rendering bugs outside 80x25 resolution --- static/js/ocelot.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/js/ocelot.js b/static/js/ocelot.js index 238618e..7d464a2 100644 --- a/static/js/ocelot.js +++ b/static/js/ocelot.js @@ -114,7 +114,7 @@ function fillChar(char, px, py) { var matrix = char != 32 && char != 0 ? font[char] : undefined; for (var y = 0; y < charHeight; y++) { for (var x = 0; x < charWidth; x++) { - var index = ((y + py) * pixelWidth + x + px) * 4; + var index = ((y + py) * contextData.width + x + px) * 4; var filled = false; if (matrix) { var matrixIndex = (y * charWidth + x) -- GitLab From fa983e04105458ee53c544f91555ad6474176fc6 Mon Sep 17 00:00:00 2001 From: Ponali Date: Thu, 14 Aug 2025 09:03:17 +0200 Subject: [PATCH 2/6] Add support for GPU rendering buffers --- .../totoro/ocelot/online/Workspace.scala | 50 +++++++++++++------ static/js/ocelot.js | 32 ++++++++---- 2 files changed, 56 insertions(+), 26 deletions(-) diff --git a/src/main/scala/totoro/ocelot/online/Workspace.scala b/src/main/scala/totoro/ocelot/online/Workspace.scala index 9817dab..a35f067 100644 --- a/src/main/scala/totoro/ocelot/online/Workspace.scala +++ b/src/main/scala/totoro/ocelot/online/Workspace.scala @@ -84,6 +84,9 @@ class Workspace { producer offer TextMessage(s"fill\n${event.x}\n${event.y}\n${event.width}\n${event.height}\n${Character.toChars(event.codePoint).mkString}") case event: TextBufferSetResolutionEvent => producer offer TextMessage(s"resolution\n${event.width}\n${event.height}") + case event: TextBufferBitBltEvent => + sendArea(event.x-1, event.y-1, event.width, event.height, event.x-1, event.y-1) + // Ocelot.log.debug(s"bitblt ${event.fromX-1},${event.fromY-1} ${event.width}x${event.height} ${event.x-1},${event.y-1}") } } @@ -145,17 +148,7 @@ class Workspace { screen.data.format.inflate(screen.data.format.deflate(value)) } - def sendState(): Unit = { - val state = new StringBuilder("state\n") - - // write current resolution - state ++= screen.data.width + "\n" - state ++= screen.data.height + "\n" - - // write current colors - state ++= getColor(screen.data.foreground).toString + "\n" - state ++= getColor(screen.data.background).toString + "\n" - + def encodeArea(state: StringBuilder, areaX1: Int, areaY1: Int, areaX2: Int, areaY2: Int): Unit = { // write the matrix, optimized as a bunch of `set` operations var lastColor: Short = -1 var lastX: Int = -1 @@ -175,8 +168,8 @@ class Workspace { } } - for (y <- 0 until screen.data.height) { - for (x <- 0 until screen.data.width) { + for (y <- areaY1.min(screen.data.height-1).max(0) to areaY2.min(screen.data.height-1).max(0)) { + for (x <- areaX1.min(screen.data.width-1).max(0) to areaX2.min(screen.data.width-1).max(0)) { val currentColor = screen.data.color(y)(x) val currentCodePoint = screen.data.buffer(y)(x) if (currentColor != lastColor) { @@ -184,8 +177,8 @@ class Workspace { set() } lastColor = currentColor - lastX = x - lastY = y + lastX = x-areaX1 + lastY = y-areaY1 } value ++= Character.toChars(currentCodePoint) } @@ -195,6 +188,33 @@ class Workspace { } // write the last one set set() + } + + def sendState(): Unit = { + val state = new StringBuilder("state\n") + + // write current resolution + state ++= screen.data.width + "\n" + state ++= screen.data.height + "\n" + + // write current colors + state ++= getColor(screen.data.foreground).toString + "\n" + state ++= getColor(screen.data.background).toString + "\n" + + encodeArea(state, 0, 0, screen.data.width, screen.data.height) + + // send + producer offer TextMessage(state.result()) + } + + def sendArea(areaX: Int, areaY: Int, areaWidth: Int, areaHeight: Int, showX: Int, showY: Int): Unit = { + val state = new StringBuilder("area\n") + + // write area position when shown to the screen + state ++= showX + "\n" + state ++= showY + "\n" + + encodeArea(state, areaX, areaY, areaX+areaWidth, areaY+areaHeight) // send producer offer TextMessage(state.result()) diff --git a/static/js/ocelot.js b/static/js/ocelot.js index 7d464a2..e9df7fa 100644 --- a/static/js/ocelot.js +++ b/static/js/ocelot.js @@ -201,6 +201,20 @@ function calculateBounds() { bounds.height = rect.height - verticalBorder * 2; } +function renderArea(parts, areaX = 0, areaY = 0) { + for (var i = 0; i < parts.length; i += 5) { + if (i + 4 >= parts.length) break; + var x = parseInt(parts[i]); + var y = parseInt(parts[i + 1]); + var f = numberToColour(parseInt(parts[i + 2])); + var b = numberToColour(parseInt(parts[i + 3])); + var value = parts[i + 4]; + setForeground(f[0], f[1], f[2]); + setBackground(b[0], b[1], b[2]); + set(x+areaX, y+areaY, value, false, false); + } +} + // network // --------------------------------------------------------------------------------- // var socket; @@ -254,22 +268,18 @@ function subscribeOnSocketEvents() { var fore = numberToColour(parseInt(parts[3])); var back = numberToColour(parseInt(parts[4])); // read and apply all changes - for (var i = 5; i < parts.length; i += 5) { - if (i + 4 >= parts.length) break; - var x = parseInt(parts[i]); - var y = parseInt(parts[i + 1]); - var f = numberToColour(parseInt(parts[i + 2])); - var b = numberToColour(parseInt(parts[i + 3])); - var value = parts[i + 4]; - setForeground(f[0], f[1], f[2]); - setBackground(b[0], b[1], b[2]); - set(x, y, value, false, false); - } + renderArea(parts.slice(5)) // set colors to current setForeground(fore[0], fore[1], fore[2]); setBackground(back[0], back[1], back[2]); flush(); break; + case 'area': + var areaX = parseInt(parts[1]); + var areaY = parseInt(parts[2]); + renderArea(parts.slice(3),areaX,areaY); + flush(); + break; case 'turnon-failure': turnOnButton.classList.remove('warning'); void turnOnButton.offsetWidth; // black magic - triggering element reflow -- GitLab From f5e1c849f048e02a31196878d9d9dac7685a8d0b Mon Sep 17 00:00:00 2001 From: Ponali Date: Thu, 14 Aug 2025 09:50:05 +0200 Subject: [PATCH 3/6] Fix syncing bug with non-printable newline character under 'state' and 'area' events --- src/main/scala/totoro/ocelot/online/Workspace.scala | 4 +++- static/js/ocelot.js | 6 +++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/main/scala/totoro/ocelot/online/Workspace.scala b/src/main/scala/totoro/ocelot/online/Workspace.scala index a35f067..3937db6 100644 --- a/src/main/scala/totoro/ocelot/online/Workspace.scala +++ b/src/main/scala/totoro/ocelot/online/Workspace.scala @@ -148,6 +148,8 @@ class Workspace { screen.data.format.inflate(screen.data.format.deflate(value)) } + def escapeLineFeed(str: String): String = str.replace("\\","\\\\").replace("\n","\\n") + def encodeArea(state: StringBuilder, areaX1: Int, areaY1: Int, areaX2: Int, areaY2: Int): Unit = { // write the matrix, optimized as a bunch of `set` operations var lastColor: Short = -1 @@ -163,7 +165,7 @@ class Workspace { val back = PackedColor.unpackBackground(lastColor, screen.data.format) state ++= fore.toString + "\n" state ++= back.toString + "\n" - state ++= value.result() + "\n" + state ++= escapeLineFeed(value.result()) + "\n" value.clear() } } diff --git a/static/js/ocelot.js b/static/js/ocelot.js index e9df7fa..9f8941e 100644 --- a/static/js/ocelot.js +++ b/static/js/ocelot.js @@ -201,6 +201,10 @@ function calculateBounds() { bounds.height = rect.height - verticalBorder * 2; } +function unescape(str) { + return str.replaceAll("\\n","\n").replaceAll("\\\\","\\") +} + function renderArea(parts, areaX = 0, areaY = 0) { for (var i = 0; i < parts.length; i += 5) { if (i + 4 >= parts.length) break; @@ -208,7 +212,7 @@ function renderArea(parts, areaX = 0, areaY = 0) { var y = parseInt(parts[i + 1]); var f = numberToColour(parseInt(parts[i + 2])); var b = numberToColour(parseInt(parts[i + 3])); - var value = parts[i + 4]; + var value = unescape(parts[i + 4]); setForeground(f[0], f[1], f[2]); setBackground(b[0], b[1], b[2]); set(x+areaX, y+areaY, value, false, false); -- GitLab From 0a448bfd9cda4a8721677f63dc8d55cbdd1d3958 Mon Sep 17 00:00:00 2001 From: Ponali Date: Thu, 14 Aug 2025 11:30:49 +0200 Subject: [PATCH 4/6] Add support for wide characters --- .../totoro/ocelot/online/Workspace.scala | 20 +++++++++++-- static/js/ocelot.js | 28 ++++++++++++++----- 2 files changed, 38 insertions(+), 10 deletions(-) diff --git a/src/main/scala/totoro/ocelot/online/Workspace.scala b/src/main/scala/totoro/ocelot/online/Workspace.scala index 3937db6..25def39 100644 --- a/src/main/scala/totoro/ocelot/online/Workspace.scala +++ b/src/main/scala/totoro/ocelot/online/Workspace.scala @@ -6,7 +6,7 @@ import totoro.ocelot.brain.entity.{CPU, Case, GraphicsCard, HDDManaged, HDDUnman import totoro.ocelot.brain.event._ import totoro.ocelot.brain.loot.Loot import totoro.ocelot.brain.user.User -import totoro.ocelot.brain.util.{ExtendedTier, PackedColor, Tier} +import totoro.ocelot.brain.util.{ExtendedTier, PackedColor, Tier, FontUtils, ExtendedUnicodeHelper} import totoro.ocelot.brain.workspace.{Workspace => BSpace} import totoro.ocelot.brain.{Ocelot => Brain} @@ -60,6 +60,20 @@ class Workspace { eeprom } + def handleWideChars(str: String): String = { + // every wide character will have a space next to it, like in the screen buffer + val value: StringBuilder = new StringBuilder + val length = ExtendedUnicodeHelper.length(str); + for (i <- 0 until length) { + value += str.charAt(i); + val codePoint = str.codePointAt(i); + for (i <- 1 until FontUtils.wcwidth(codePoint)) { + value += ' '; + } + } + value.result() + } + def subscribe(producer: SourceQueueWithComplete[TextMessage]): Unit = { this.producer = producer // register some listeners @@ -69,10 +83,10 @@ class Workspace { case event: BeepPatternEvent => producer offer TextMessage(s"beep-pattern\n${event.pattern}") case event: MachineCrashEvent => - computer.inventory(8) = Loot.AdvLoaderEEPROM.create() + resetEEPROM() producer offer TextMessage(s"crash\n${event.message}") case event: TextBufferSetEvent => - producer offer TextMessage(s"set\n${event.x}\n${event.y}\n${event.vertical}\n${event.value}") + producer offer TextMessage(s"set\n${event.x}\n${event.y}\n${event.vertical}\n${handleWideChars(event.value)}") case event: TextBufferSetForegroundColorEvent => producer offer TextMessage(s"foreground\n${event.color}") case event: TextBufferSetBackgroundColorEvent => diff --git a/static/js/ocelot.js b/static/js/ocelot.js index 9f8941e..ef41306 100644 --- a/static/js/ocelot.js +++ b/static/js/ocelot.js @@ -110,14 +110,25 @@ function updateContextData() { data = contextData.data; } +function isWide(char) { + var matrix = char != 32 && char != 0 ? font[char] : undefined; + if (matrix==undefined) return false; + return matrix.length>16; +} + function fillChar(char, px, py) { var matrix = char != 32 && char != 0 ? font[char] : undefined; + var pixelCount = matrix ? matrix.length*8 : 0 + var cw = Math.max(Math.ceil(pixelCount/charHeight),charWidth) for (var y = 0; y < charHeight; y++) { - for (var x = 0; x < charWidth; x++) { - var index = ((y + py) * contextData.width + x + px) * 4; + for (var x = 0; x < cw; x++) { + var vx = x + px; + var vy = y + py; + if (vx < 0 || vx>=contextData.width || vy < 0 || vy>=contextData.height) continue; + var index = (vy * contextData.width + vx) * 4; var filled = false; if (matrix) { - var matrixIndex = (y * charWidth + x) + var matrixIndex = (y * cw + x) filled = bit(matrix[Math.floor(matrixIndex / 8)], 7 - matrixIndex % 8); } if (filled) { @@ -137,10 +148,13 @@ function fillChar(char, px, py) { function fillText(text, px, py, vertical = false) { for (var i = 0; i < text.length; i++) { - if (vertical) - fillChar(text.charCodeAt(i), px, py + i * charHeight); - else - fillChar(text.charCodeAt(i), px + i * charWidth, py); + var charCode = text.charCodeAt(i) + if (vertical) { + fillChar(charCode, px, py + i * charHeight); + } else { + fillChar(charCode, px + i * charWidth, py); + if(isWide(charCode)) i = i + 1; + } } } -- GitLab From 6ca80266a6cb3c38a0c4f0744a72456dec61cf47 Mon Sep 17 00:00:00 2001 From: Ponali Date: Sat, 16 Aug 2025 06:38:07 +0200 Subject: [PATCH 5/6] Fixed unicode support with `handleWideChars` function, and removed semicolons --- src/main/scala/totoro/ocelot/online/Workspace.scala | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/main/scala/totoro/ocelot/online/Workspace.scala b/src/main/scala/totoro/ocelot/online/Workspace.scala index 25def39..ac3beb7 100644 --- a/src/main/scala/totoro/ocelot/online/Workspace.scala +++ b/src/main/scala/totoro/ocelot/online/Workspace.scala @@ -63,12 +63,11 @@ class Workspace { def handleWideChars(str: String): String = { // every wide character will have a space next to it, like in the screen buffer val value: StringBuilder = new StringBuilder - val length = ExtendedUnicodeHelper.length(str); - for (i <- 0 until length) { - value += str.charAt(i); - val codePoint = str.codePointAt(i); + val length = ExtendedUnicodeHelper.length(str) + for (codePoint <- str.codePoints) { + value += codePoint.toChar() for (i <- 1 until FontUtils.wcwidth(codePoint)) { - value += ' '; + value += ' ' } } value.result() -- GitLab From 27b46d7e56f297957d0124cd57b94f61cfe81aee Mon Sep 17 00:00:00 2001 From: Ponali Date: Sat, 16 Aug 2025 08:54:12 +0200 Subject: [PATCH 6/6] Fix compile error with Unicode iteration I should've tested my code before pushing... --- src/main/scala/totoro/ocelot/online/Workspace.scala | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/scala/totoro/ocelot/online/Workspace.scala b/src/main/scala/totoro/ocelot/online/Workspace.scala index ac3beb7..6b4ad13 100644 --- a/src/main/scala/totoro/ocelot/online/Workspace.scala +++ b/src/main/scala/totoro/ocelot/online/Workspace.scala @@ -64,8 +64,10 @@ class Workspace { // every wide character will have a space next to it, like in the screen buffer val value: StringBuilder = new StringBuilder val length = ExtendedUnicodeHelper.length(str) - for (codePoint <- str.codePoints) { - value += codePoint.toChar() + val iterator = str.codePoints.iterator() + while (iterator.hasNext()) { + val codePoint = iterator.next() + value += codePoint.toChar for (i <- 1 until FontUtils.wcwidth(codePoint)) { value += ' ' } -- GitLab