From 40de62056be4e84e61a623841b09b3a2a56dd11d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20N=C3=BCcke?= Date: Wed, 14 Jan 2015 18:53:19 +0100 Subject: [PATCH] Added methods for raw text and color setting to TextBuffer API. --- src/main/java/li/cil/oc/api/API.java | 2 +- .../li/cil/oc/api/component/TextBuffer.java | 65 +++++++ .../li/cil/oc/client/PacketHandler.scala | 57 ++++++ .../scala/li/cil/oc/common/PacketType.scala | 3 + .../cil/oc/common/component/TextBuffer.scala | 175 ++++++++++++------ .../scala/li/cil/oc/server/PacketSender.scala | 45 +++++ .../scala/li/cil/oc/util/PackedColor.scala | 10 +- 7 files changed, 296 insertions(+), 61 deletions(-) diff --git a/src/main/java/li/cil/oc/api/API.java b/src/main/java/li/cil/oc/api/API.java index 7c4dbaebc..eec93c4d0 100644 --- a/src/main/java/li/cil/oc/api/API.java +++ b/src/main/java/li/cil/oc/api/API.java @@ -11,7 +11,7 @@ import li.cil.oc.api.detail.*; */ public class API { public static final String ID_OWNER = "OpenComputers|Core"; - public static final String VERSION = "4.2.0"; + public static final String VERSION = "4.2.1"; public static DriverAPI driver = null; public static FileSystemAPI fileSystem = null; diff --git a/src/main/java/li/cil/oc/api/component/TextBuffer.java b/src/main/java/li/cil/oc/api/component/TextBuffer.java index c7297c631..167bc937f 100644 --- a/src/main/java/li/cil/oc/api/component/TextBuffer.java +++ b/src/main/java/li/cil/oc/api/component/TextBuffer.java @@ -341,6 +341,71 @@ public interface TextBuffer extends ManagedEnvironment, Persistable { */ boolean isBackgroundFromPalette(int column, int row); + /** + * Overwrites a portion of the text in raw mode. + *

+ * This will copy the given char array into the buffer, starting at the + * specified column and row. The array is expected to be indexed row- + * first, i.e. the first dimension is the vertical axis, the second + * the horizontal. + *

+ * Important: this performs no checks as to whether something + * actually changed. It will always send the changed patch to clients. + * It will also not crop the specified array to the actually used range. + * In other words, this is not intended to be exposed as-is to user code, + * it should always be called with validated, and, as necessary, cropped + * values. + * + * @param column the horizontal index. + * @param row the vertical index. + * @param text the text to write. + */ + void rawSetText(int column, int row, char[][] text); + + /** + * Overwrites a portion of the foreground color information in raw mode. + *

+ * This will convert the specified RGB data (in 0xRRGGBB format) + * to the internal, packed representation and copy it into the buffer, + * starting at the specified column and row. The array is expected to be + * indexed row-first, i.e. the first dimension is the vertical axis, the + * second the horizontal. + *

+ * Important: this performs no checks as to whether something + * actually changed. It will always send the changed patch to clients. + * It will also not crop the specified array to the actually used range. + * In other words, this is not intended to be exposed as-is to user code, + * it should always be called with validated, and, as necessary, cropped + * values. + * + * @param column the horizontal index. + * @param row the vertical index. + * @param color the foreground color data to write. + */ + void rawSetForeground(int column, int row, int[][] color); + + /** + * Overwrites a portion of the background color information in raw mode. + *

+ * This will convert the specified RGB data (in 0xRRGGBB format) + * to the internal, packed representation and copy it into the buffer, + * starting at the specified column and row. The array is expected to be + * indexed row-first, i.e. the first dimension is the vertical axis, the + * second the horizontal. + *

+ * Important: this performs no checks as to whether something + * actually changed. It will always send the changed patch to clients. + * It will also not crop the specified array to the actually used range. + * In other words, this is not intended to be exposed as-is to user code, + * it should always be called with validated, and, as necessary, cropped + * values. + * + * @param column the horizontal index. + * @param row the vertical index. + * @param color the background color data to write. + */ + void rawSetBackground(int column, int row, int[][] color); + // ----------------------------------------------------------------------- // /** diff --git a/src/main/scala/li/cil/oc/client/PacketHandler.scala b/src/main/scala/li/cil/oc/client/PacketHandler.scala index 20177011e..da0dc4f38 100644 --- a/src/main/scala/li/cil/oc/client/PacketHandler.scala +++ b/src/main/scala/li/cil/oc/client/PacketHandler.scala @@ -370,6 +370,9 @@ object PacketHandler extends CommonPacketHandler { case PacketType.TextBufferMultiResolutionChange => onTextBufferMultiResolutionChange(p, buffer) case PacketType.TextBufferMultiMaxResolutionChange => onTextBufferMultiMaxResolutionChange(p, buffer) case PacketType.TextBufferMultiSet => onTextBufferMultiSet(p, buffer) + case PacketType.TextBufferMultiRawSetText => onTextBufferMultiRawSetText(p, buffer) + case PacketType.TextBufferMultiRawSetBackground => onTextBufferMultiRawSetBackground(p, buffer) + case PacketType.TextBufferMultiRawSetForeground => onTextBufferMultiRawSetForeground(p, buffer) case _ => // Invalid packet. } } @@ -441,6 +444,60 @@ object PacketHandler extends CommonPacketHandler { buffer.set(col, row, s, vertical) } + def onTextBufferMultiRawSetText(p: PacketParser, buffer: component.TextBuffer) { + val col = p.readInt() + val row = p.readInt() + + val rows = p.readShort() + val text = new Array[Array[Char]](rows) + for (y <- 0 until rows) { + val cols = p.readShort() + val line = new Array[Char](cols) + for (x <- 0 until cols) { + line(x) = p.readChar() + } + text(y) = line + } + + buffer.rawSetText(col, row, text) + } + + def onTextBufferMultiRawSetBackground(p: PacketParser, buffer: component.TextBuffer) { + val col = p.readInt() + val row = p.readInt() + + val rows = p.readShort() + val color = new Array[Array[Int]](rows) + for (y <- 0 until rows) { + val cols = p.readShort() + val line = new Array[Int](cols) + for (x <- 0 until cols) { + line(x) = p.readInt() + } + color(y) = line + } + + buffer.rawSetBackground(col, row, color) + } + + def onTextBufferMultiRawSetForeground(p: PacketParser, buffer: component.TextBuffer) { + val col = p.readInt() + val row = p.readInt() + + val rows = p.readShort() + val color = new Array[Array[Int]](rows) + for (y <- 0 until rows) { + val cols = p.readShort() + val line = new Array[Int](cols) + for (x <- 0 until cols) { + line(x) = p.readInt() + } + color(y) = line + } + + buffer.rawSetForeground(col, row, color) + } + def onScreenTouchMode(p: PacketParser) = p.readTileEntity[Screen]() match { case Some(t) => t.invertTouchMode = p.readBoolean() diff --git a/src/main/scala/li/cil/oc/common/PacketType.scala b/src/main/scala/li/cil/oc/common/PacketType.scala index f08c3d653..c925def9d 100644 --- a/src/main/scala/li/cil/oc/common/PacketType.scala +++ b/src/main/scala/li/cil/oc/common/PacketType.scala @@ -41,6 +41,9 @@ object PacketType extends Enumeration { TextBufferMultiResolutionChange, TextBufferMultiMaxResolutionChange, TextBufferMultiSet, + TextBufferMultiRawSetText, + TextBufferMultiRawSetBackground, + TextBufferMultiRawSetForeground, TextBufferPowerChange, ScreenTouchMode, ServerPresence, diff --git a/src/main/scala/li/cil/oc/common/component/TextBuffer.scala b/src/main/scala/li/cil/oc/common/component/TextBuffer.scala index 68c3e4953..ea65f0669 100644 --- a/src/main/scala/li/cil/oc/common/component/TextBuffer.scala +++ b/src/main/scala/li/cil/oc/common/component/TextBuffer.scala @@ -189,7 +189,7 @@ class TextBuffer(val host: EnvironmentHost) extends prefab.ManagedEnvironment wi if (height < 1) throw new IllegalArgumentException("height must be larger or equal to one") maxResolution = (width, height) fullyLitCost = computeFullyLitCost() - proxy.onScreenMaxResolutionChange(width, width) + proxy.onBufferMaxResolutionChange(width, width) } override def getMaximumWidth = maxResolution._1 @@ -205,7 +205,7 @@ class TextBuffer(val host: EnvironmentHost) extends prefab.ManagedEnvironment wi if (w < 1 || h < 1 || w > mw || h > mw || h * w > mw * mh) throw new IllegalArgumentException("unsupported resolution") // Always send to clients, their state might be dirty. - proxy.onScreenResolutionChange(w, h) + proxy.onBufferResolutionChange(w, h) if (data.size = (w, h)) { if (node != null) { node.sendToReachable("computer.signal", "screen_resized", Int.box(w), Int.box(h)) @@ -227,7 +227,7 @@ class TextBuffer(val host: EnvironmentHost) extends prefab.ManagedEnvironment wi if (depth.ordinal > maxDepth.ordinal) throw new IllegalArgumentException("unsupported depth") // Always send to clients, their state might be dirty. - proxy.onScreenDepthChange(depth) + proxy.onBufferDepthChange(depth) data.format = PackedColor.Depth.format(depth) } @@ -236,7 +236,7 @@ class TextBuffer(val host: EnvironmentHost) extends prefab.ManagedEnvironment wi override def setPaletteColor(index: Int, color: Int) = data.format match { case palette: PackedColor.MutablePaletteFormat => palette(index) = color - proxy.onScreenPaletteChange(index) + proxy.onBufferPaletteChange(index) case _ => throw new Exception("palette not available") } @@ -251,7 +251,7 @@ class TextBuffer(val host: EnvironmentHost) extends prefab.ManagedEnvironment wi val value = PackedColor.Color(color, isFromPalette) if (data.foreground != value) { data.foreground = value - proxy.onScreenColorChange() + proxy.onBufferColorChange() } } @@ -265,7 +265,7 @@ class TextBuffer(val host: EnvironmentHost) extends prefab.ManagedEnvironment wi val value = PackedColor.Color(color, isFromPalette) if (data.background != value) { data.background = value - proxy.onScreenColorChange() + proxy.onBufferColorChange() } } @@ -275,27 +275,28 @@ class TextBuffer(val host: EnvironmentHost) extends prefab.ManagedEnvironment wi def copy(col: Int, row: Int, w: Int, h: Int, tx: Int, ty: Int) = if (data.copy(col, row, w, h, tx, ty)) - proxy.onScreenCopy(col, row, w, h, tx, ty) + proxy.onBufferCopy(col, row, w, h, tx, ty) def fill(col: Int, row: Int, w: Int, h: Int, c: Char) = if (data.fill(col, row, w, h, c)) - proxy.onScreenFill(col, row, w, h, c) + proxy.onBufferFill(col, row, w, h, c) - def set(col: Int, row: Int, s: String, vertical: Boolean) = if (col < data.width && (col >= 0 || -col < s.length)) { - // Make sure the string isn't longer than it needs to be, in particular to - // avoid sending too much data to our clients. - val (x, y, truncated) = - if (vertical) { - if (row < 0) (col, 0, s.substring(-row)) - else (col, row, s.substring(0, math.min(s.length, data.height - row))) - } - else { - if (col < 0) (0, row, s.substring(-col)) - else (col, row, s.substring(0, math.min(s.length, data.width - col))) - } - if (data.set(x, y, truncated, vertical)) - proxy.onScreenSet(x, row, truncated, vertical) - } + def set(col: Int, row: Int, s: String, vertical: Boolean): Unit = + if (col < data.width && (col >= 0 || -col < s.length)) { + // Make sure the string isn't longer than it needs to be, in particular to + // avoid sending too much data to our clients. + val (x, y, truncated) = + if (vertical) { + if (row < 0) (col, 0, s.substring(-row)) + else (col, row, s.substring(0, math.min(s.length, data.height - row))) + } + else { + if (col < 0) (0, row, s.substring(-col)) + else (col, row, s.substring(0, math.min(s.length, data.width - col))) + } + if (data.set(x, y, truncated, vertical)) + proxy.onBufferSet(x, row, truncated, vertical) + } def get(col: Int, row: Int) = data.get(col, row) @@ -321,6 +322,40 @@ class TextBuffer(val host: EnvironmentHost) extends prefab.ManagedEnvironment wi override def isBackgroundFromPalette(column: Int, row: Int) = data.format.isFromPalette(PackedColor.extractBackground(color(column, row))) + override def rawSetText(col: Int, row: Int, text: Array[Array[Char]]): Unit = { + for (y <- row until ((row + text.length) min data.height)) { + val line = text(y - row) + Array.copy(line, 0, data.buffer(y), col, line.length min data.width) + } + proxy.onBufferRawSetText(col, row, text) + } + + override def rawSetBackground(col: Int, row: Int, color: Array[Array[Int]]): Unit = { + for (y <- row until ((row + color.length) min data.height)) { + val line = color(y - row) + for (x <- col until ((col + line.length) min data.width)) { + val packedBackground = data.color(row)(col) & 0x00FF + val packedForeground = (data.format.deflate(PackedColor.Color(line(x - col))) << PackedColor.ForegroundShift) & 0xFF00 + data.color(row)(col) = (packedForeground | packedBackground).toShort + } + } + // TODO Better for bandwidth to send packed shorts here. Would need a special case for handling on client, though... + proxy.onBufferRawSetBackground(col, row, color) + } + + override def rawSetForeground(col: Int, row: Int, color: Array[Array[Int]]): Unit = { + for (y <- row until ((row + color.length) min data.height)) { + val line = color(y - row) + for (x <- col until ((col + line.length) min data.width)) { + val packedBackground = data.format.deflate(PackedColor.Color(line(x - col))) & 0x00FF + val packedForeground = data.color(row)(col) & 0xFF00 + data.color(row)(col) = (packedForeground | packedBackground).toShort + } + } + // TODO Better for bandwidth to send packed shorts here. Would need a special case for handling on client, though... + proxy.onBufferRawSetForeground(col, row, color) + } + private def color(column: Int, row: Int) = { if (column < 0 || column >= getWidth || row < 0 || row >= getHeight) throw new IndexOutOfBoundsException() @@ -477,28 +512,40 @@ object TextBuffer { def render() = false - def onScreenColorChange(): Unit + def onBufferColorChange(): Unit - def onScreenCopy(col: Int, row: Int, w: Int, h: Int, tx: Int, ty: Int) { + def onBufferCopy(col: Int, row: Int, w: Int, h: Int, tx: Int, ty: Int) { owner.relativeLitArea = -1 } - def onScreenDepthChange(depth: ColorDepth): Unit + def onBufferDepthChange(depth: ColorDepth): Unit - def onScreenFill(col: Int, row: Int, w: Int, h: Int, c: Char) { + def onBufferFill(col: Int, row: Int, w: Int, h: Int, c: Char) { owner.relativeLitArea = -1 } - def onScreenPaletteChange(index: Int): Unit + def onBufferPaletteChange(index: Int): Unit - def onScreenResolutionChange(w: Int, h: Int) { + def onBufferResolutionChange(w: Int, h: Int) { owner.relativeLitArea = -1 } - def onScreenMaxResolutionChange(w: Int, h: Int) { + def onBufferMaxResolutionChange(w: Int, h: Int) { } - def onScreenSet(col: Int, row: Int, s: String, vertical: Boolean) { + def onBufferSet(col: Int, row: Int, s: String, vertical: Boolean) { + owner.relativeLitArea = -1 + } + + def onBufferRawSetText(col: Int, row: Int, text: Array[Array[Char]]) { + owner.relativeLitArea = -1 + } + + def onBufferRawSetBackground(col: Int, row: Int, color: Array[Array[Int]]) { + owner.relativeLitArea = -1 + } + + def onBufferRawSetForeground(col: Int, row: Int, color: Array[Array[Int]]) { owner.relativeLitArea = -1 } @@ -532,35 +579,35 @@ object TextBuffer { wasDirty } - override def onScreenColorChange() { + override def onBufferColorChange() { markDirty() } - override def onScreenCopy(col: Int, row: Int, w: Int, h: Int, tx: Int, ty: Int) { - super.onScreenCopy(col, row, w, h, tx, ty) + override def onBufferCopy(col: Int, row: Int, w: Int, h: Int, tx: Int, ty: Int) { + super.onBufferCopy(col, row, w, h, tx, ty) markDirty() } - override def onScreenDepthChange(depth: ColorDepth) { + override def onBufferDepthChange(depth: ColorDepth) { markDirty() } - override def onScreenFill(col: Int, row: Int, w: Int, h: Int, c: Char) { - super.onScreenFill(col, row, w, h, c) + override def onBufferFill(col: Int, row: Int, w: Int, h: Int, c: Char) { + super.onBufferFill(col, row, w, h, c) markDirty() } - override def onScreenPaletteChange(index: Int) { + override def onBufferPaletteChange(index: Int) { markDirty() } - override def onScreenResolutionChange(w: Int, h: Int) { - super.onScreenResolutionChange(w, h) + override def onBufferResolutionChange(w: Int, h: Int) { + super.onBufferResolutionChange(w, h) markDirty() } - override def onScreenSet(col: Int, row: Int, s: String, vertical: Boolean) { - super.onScreenSet(col, row, s, vertical) + override def onBufferSet(col: Int, row: Int, s: String, vertical: Boolean) { + super.onBufferSet(col, row, s, vertical) dirty = true } @@ -609,53 +656,71 @@ object TextBuffer { } class ServerProxy(val owner: TextBuffer) extends Proxy { - override def onScreenColorChange() { + override def onBufferColorChange() { owner.host.markChanged() owner.synchronized(ServerPacketSender.appendTextBufferColorChange(owner.pendingCommands, owner.data.foreground, owner.data.background)) } - override def onScreenCopy(col: Int, row: Int, w: Int, h: Int, tx: Int, ty: Int) { - super.onScreenCopy(col, row, w, h, tx, ty) + override def onBufferCopy(col: Int, row: Int, w: Int, h: Int, tx: Int, ty: Int) { + super.onBufferCopy(col, row, w, h, tx, ty) owner.host.markChanged() owner.synchronized(ServerPacketSender.appendTextBufferCopy(owner.pendingCommands, col, row, w, h, tx, ty)) } - override def onScreenDepthChange(depth: ColorDepth) { + override def onBufferDepthChange(depth: ColorDepth) { owner.host.markChanged() owner.synchronized(ServerPacketSender.appendTextBufferDepthChange(owner.pendingCommands, depth)) } - override def onScreenFill(col: Int, row: Int, w: Int, h: Int, c: Char) { - super.onScreenFill(col, row, w, h, c) + override def onBufferFill(col: Int, row: Int, w: Int, h: Int, c: Char) { + super.onBufferFill(col, row, w, h, c) owner.host.markChanged() owner.synchronized(ServerPacketSender.appendTextBufferFill(owner.pendingCommands, col, row, w, h, c)) } - override def onScreenPaletteChange(index: Int) { + override def onBufferPaletteChange(index: Int) { owner.host.markChanged() owner.synchronized(ServerPacketSender.appendTextBufferPaletteChange(owner.pendingCommands, index, owner.getPaletteColor(index))) } - override def onScreenResolutionChange(w: Int, h: Int) { - super.onScreenResolutionChange(w, h) + override def onBufferResolutionChange(w: Int, h: Int) { + super.onBufferResolutionChange(w, h) owner.host.markChanged() owner.synchronized(ServerPacketSender.appendTextBufferResolutionChange(owner.pendingCommands, w, h)) } - override def onScreenMaxResolutionChange(w: Int, h: Int) { + override def onBufferMaxResolutionChange(w: Int, h: Int) { if (owner.node.network != null) { - super.onScreenMaxResolutionChange(w, h) + super.onBufferMaxResolutionChange(w, h) owner.host.markChanged() owner.synchronized(ServerPacketSender.appendTextBufferMaxResolutionChange(owner.pendingCommands, w, h)) } } - override def onScreenSet(col: Int, row: Int, s: String, vertical: Boolean) { - super.onScreenSet(col, row, s, vertical) + override def onBufferSet(col: Int, row: Int, s: String, vertical: Boolean) { + super.onBufferSet(col, row, s, vertical) owner.host.markChanged() owner.synchronized(ServerPacketSender.appendTextBufferSet(owner.pendingCommands, col, row, s, vertical)) } + override def onBufferRawSetText(col: Int, row: Int, text: Array[Array[Char]]) { + super.onBufferRawSetText(col, row, text) + owner.host.markChanged() + owner.synchronized(ServerPacketSender.appendTextBufferRawSetText(owner.pendingCommands, col, row, text)) + } + + override def onBufferRawSetBackground(col: Int, row: Int, color: Array[Array[Int]]) { + super.onBufferRawSetBackground(col, row, color) + owner.host.markChanged() + owner.synchronized(ServerPacketSender.appendTextBufferRawSetBackground(owner.pendingCommands, col, row, color)) + } + + override def onBufferRawSetForeground(col: Int, row: Int, color: Array[Array[Int]]) { + super.onBufferRawSetForeground(col, row, color) + owner.host.markChanged() + owner.synchronized(ServerPacketSender.appendTextBufferRawSetForeground(owner.pendingCommands, col, row, color)) + } + override def keyDown(character: Char, code: Int, player: EntityPlayer) { sendToKeyboards("keyboard.keyDown", player, Char.box(character), Int.box(code)) } diff --git a/src/main/scala/li/cil/oc/server/PacketSender.scala b/src/main/scala/li/cil/oc/server/PacketSender.scala index 25240c607..b7981467e 100644 --- a/src/main/scala/li/cil/oc/server/PacketSender.scala +++ b/src/main/scala/li/cil/oc/server/PacketSender.scala @@ -402,6 +402,51 @@ object PacketSender { pb.writeBoolean(vertical) } + def appendTextBufferRawSetText(pb: PacketBuilder, col: Int, row: Int, text: Array[Array[Char]]) { + pb.writePacketType(PacketType.TextBufferMultiRawSetText) + + pb.writeInt(col) + pb.writeInt(row) + pb.writeShort(text.length.toShort) + for (y <- 0 until text.length.toShort) { + val line = text(y) + pb.writeShort(line.length.toShort) + for (x <- 0 until line.length.toShort) { + pb.writeChar(line(x)) + } + } + } + + def appendTextBufferRawSetBackground(pb: PacketBuilder, col: Int, row: Int, color: Array[Array[Int]]) { + pb.writePacketType(PacketType.TextBufferMultiRawSetBackground) + + pb.writeInt(col) + pb.writeInt(row) + pb.writeShort(color.length.toShort) + for (y <- 0 until color.length.toShort) { + val line = color(y) + pb.writeShort(line.length.toShort) + for (x <- 0 until line.length.toShort) { + pb.writeInt(line(x)) + } + } + } + + def appendTextBufferRawSetForeground(pb: PacketBuilder, col: Int, row: Int, color: Array[Array[Int]]) { + pb.writePacketType(PacketType.TextBufferMultiRawSetForeground) + + pb.writeInt(col) + pb.writeInt(row) + pb.writeShort(color.length.toShort) + for (y <- 0 until color.length.toShort) { + val line = color(y) + pb.writeShort(line.length.toShort) + for (x <- 0 until line.length.toShort) { + pb.writeInt(line(x)) + } + } + } + def sendTextBufferInit(address: String, value: NBTTagCompound, player: EntityPlayerMP) { val pb = new CompressedPacketBuilder(PacketType.TextBufferInit) diff --git a/src/main/scala/li/cil/oc/util/PackedColor.scala b/src/main/scala/li/cil/oc/util/PackedColor.scala index 668a077ae..8496eb867 100644 --- a/src/main/scala/li/cil/oc/util/PackedColor.scala +++ b/src/main/scala/li/cil/oc/util/PackedColor.scala @@ -165,16 +165,16 @@ object PackedColor { case class Color(value: Int, isPalette: Boolean = false) // Colors are packed: 0xFFBB (F = foreground, B = background) - private val fgShift = 8 - private val bgMask = 0x000000FF + val ForegroundShift = 8 + val BackgroundMask = 0x000000FF def pack(foreground: Color, background: Color, format: ColorFormat) = { - (((format.deflate(foreground) & 0xFF) << fgShift) | (format.deflate(background) & 0xFF)).toShort + (((format.deflate(foreground) & 0xFF) << ForegroundShift) | (format.deflate(background) & 0xFF)).toShort } - def extractForeground(color: Short) = (color & 0xFFFF) >>> fgShift + def extractForeground(color: Short) = (color & 0xFFFF) >>> ForegroundShift - def extractBackground(color: Short) = color & bgMask + def extractBackground(color: Short) = color & BackgroundMask def unpackForeground(color: Short, format: ColorFormat) = format.inflate(extractForeground(color))