From 9d38ecb51ddb728f88639d71a5ad48b4dcd75a24 Mon Sep 17 00:00:00 2001 From: payonel Date: Sat, 16 May 2020 10:48:23 -0700 Subject: [PATCH] remove packet sync for invisible gpu change - make free more free --- .../cil/oc/common/component/TextBuffer.scala | 224 +++++++----------- .../component/traits/TextBufferProxy.scala | 154 ++++++++++++ 2 files changed, 240 insertions(+), 138 deletions(-) create mode 100644 src/main/scala/li/cil/oc/common/component/traits/TextBufferProxy.scala 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 693a6ca01..dcd01663b 100644 --- a/src/main/scala/li/cil/oc/common/component/TextBuffer.scala +++ b/src/main/scala/li/cil/oc/common/component/TextBuffer.scala @@ -34,18 +34,19 @@ import net.minecraft.entity.player.EntityPlayer import net.minecraft.nbt.NBTTagCompound import net.minecraftforge.event.world.ChunkEvent import net.minecraftforge.event.world.WorldEvent +import tconstruct.client.tabs.InventoryTabVanilla import scala.collection.convert.WrapAsJava._ import scala.collection.convert.WrapAsScala._ import scala.collection.mutable -class TextBuffer(val host: EnvironmentHost) extends prefab.ManagedEnvironment with api.internal.TextBuffer with DeviceInfo { +class TextBuffer(val host: EnvironmentHost) extends prefab.ManagedEnvironment with traits.TextBufferProxy with DeviceInfo { override val node = api.Network.newNode(this, Visibility.Network). withComponent("screen"). withConnector(). create() - private var maxResolution = Settings.screenResolutionsByTier(Tier.One) + private var maxResolution: (Int, Int) = Settings.screenResolutionsByTier(Tier.One) private var maxDepth = Settings.screenDepthsByTier(Tier.One) @@ -77,26 +78,26 @@ class TextBuffer(val host: EnvironmentHost) extends prefab.ManagedEnvironment wi pb } - var fullyLitCost = computeFullyLitCost() + var fullyLitCost: Double = computeFullyLitCost() // This computes the energy cost (per tick) to keep the screen running if // every single "pixel" is lit. This cost increases with higher tiers as // their maximum resolution (pixel density) increases. For a basic screen // this is simply the configured cost. - def computeFullyLitCost() = { + def computeFullyLitCost(): Double = { val (w, h) = Settings.screenResolutionsByTier(0) val mw = getMaximumWidth val mh = getMaximumHeight powerConsumptionPerTick * (mw * mh) / (w * h) } - val proxy = + val proxy: TextBuffer.Proxy = if (SideTracker.isClient) new TextBuffer.ClientProxy(this) else new TextBuffer.ServerProxy(this) val data = new util.TextBuffer(maxResolution, PackedColor.Depth.format(maxDepth)) - var viewport = data.size + var viewport: (Int, Int) = data.size def markInitialized(): Unit = { syncCooldown = -1 // Stop polling for init state. @@ -225,7 +226,7 @@ class TextBuffer(val host: EnvironmentHost) extends prefab.ManagedEnvironment wi fullyLitCost = computeFullyLitCost() } - override def getEnergyCostPerTick = powerConsumptionPerTick + override def getEnergyCostPerTick: Double = powerConsumptionPerTick override def setPowerState(value: Boolean) { if (isDisplaying != value) { @@ -238,7 +239,7 @@ class TextBuffer(val host: EnvironmentHost) extends prefab.ManagedEnvironment wi } } - override def getPowerState = isDisplaying + override def getPowerState: Boolean = isDisplaying override def setMaximumResolution(width: Int, height: Int) { if (width < 1) throw new IllegalArgumentException("width must be larger or equal to one") @@ -248,15 +249,15 @@ class TextBuffer(val host: EnvironmentHost) extends prefab.ManagedEnvironment wi proxy.onBufferMaxResolutionChange(width, width) } - override def getMaximumWidth = maxResolution._1 + override def getMaximumWidth: Int = maxResolution._1 - override def getMaximumHeight = maxResolution._2 + override def getMaximumHeight: Int = maxResolution._2 - override def setAspectRatio(width: Double, height: Double) = this.synchronized(aspectRatio = (width, height)) + override def setAspectRatio(width: Double, height: Double): Unit = this.synchronized(aspectRatio = (width, height)) - override def getAspectRatio = aspectRatio._1 / aspectRatio._2 + override def getAspectRatio: Double = aspectRatio._1 / aspectRatio._2 - override def setResolution(w: Int, h: Int) = { + override def setResolution(w: Int, h: Int): Boolean = { val (mw, mh) = maxResolution if (w < 1 || h < 1 || w > mw || h > mw || h * w > mw * mh) throw new IllegalArgumentException("unsupported resolution") @@ -275,10 +276,6 @@ class TextBuffer(val host: EnvironmentHost) extends prefab.ManagedEnvironment wi else false } - override def getWidth = data.width - - override def getHeight = data.height - override def setViewport(w: Int, h: Int): Boolean = { val (mw, mh) = data.size if (w < 1 || h < 1 || w > mw || h > mh) @@ -300,185 +297,136 @@ class TextBuffer(val host: EnvironmentHost) extends prefab.ManagedEnvironment wi override def getViewportHeight: Int = viewport._2 - override def setMaximumColorDepth(depth: api.internal.TextBuffer.ColorDepth) = maxDepth = depth + override def setMaximumColorDepth(depth: api.internal.TextBuffer.ColorDepth): Unit = maxDepth = depth - override def getMaximumColorDepth = maxDepth + override def getMaximumColorDepth: api.internal.TextBuffer.ColorDepth = maxDepth - override def setColorDepth(depth: api.internal.TextBuffer.ColorDepth) = { - if (depth.ordinal > maxDepth.ordinal) - throw new IllegalArgumentException("unsupported depth") + override def setColorDepth(depth: api.internal.TextBuffer.ColorDepth): Boolean = { + val colorDepthChanged: Boolean = super.setColorDepth(depth) // Always send to clients, their state might be dirty. proxy.onBufferDepthChange(depth) - data.format = PackedColor.Depth.format(depth) + colorDepthChanged } - override def getColorDepth = data.format.depth + override def onBufferColorChange(): Unit = + proxy.onBufferColorChange() - override def setPaletteColor(index: Int, color: Int) = data.format match { - case palette: PackedColor.MutablePaletteFormat => - palette(index) = color - proxy.onBufferPaletteChange(index) - case _ => throw new Exception("palette not available") + class ViewportBox(val x1: Int, val y1: Int, val x2: Int, val y2: Int) { + def width: Int = (x2 - x1 + 1) max 0 + def height: Int = (y2 - y1 + 1) max 0 + def isVisible: Boolean = width > 0 && height > 0 + def isEmpty: Boolean = !isVisible } - override def getPaletteColor(index: Int) = data.format match { - case palette: PackedColor.MutablePaletteFormat => palette(index) - case _ => throw new Exception("palette not available") + // return box within viewport + private def truncateToViewport(x1: Int, y1: Int, x2: Int, y2: Int): ViewportBox = { + val x: Int = (x1 min x2) max 0 + val y: Int = (y1 min y2) max 0 + val lastX: Int = ((x1 max x2) max 0) min (viewport._1 - 1) + val lastY: Int = ((y1 max y2) max 0) min (viewport._2 - 1) + new ViewportBox(x, y, lastX, lastY) } - override def setForegroundColor(color: Int) = setForegroundColor(color, isFromPalette = false) - - override def setForegroundColor(color: Int, isFromPalette: Boolean) { - val value = PackedColor.Color(color, isFromPalette) - if (data.foreground != value) { - data.foreground = value - proxy.onBufferColorChange() + override def onBufferCopy(col: Int, row: Int, w: Int, h: Int, tx: Int, ty: Int): Unit = { + // only notify about viewport changes + val box = truncateToViewport(col + tx, row + ty, col + tx + w - 1, row + ty + h - 1) + if (box.isVisible) { + proxy.onBufferCopy(col, row, box.width, box.height, tx, ty) } } - override def getForegroundColor = data.foreground.value - - override def isForegroundFromPalette = data.foreground.isPalette - - override def setBackgroundColor(color: Int) = setBackgroundColor(color, isFromPalette = false) - - override def setBackgroundColor(color: Int, isFromPalette: Boolean) { - val value = PackedColor.Color(color, isFromPalette) - if (data.background != value) { - data.background = value - proxy.onBufferColorChange() + override def onBufferFill(col: Int, row: Int, w: Int, h: Int, c: Char): Unit = { + val box = truncateToViewport(col, row, col + w - 1, row + h - 1) + if (box.isVisible) { + proxy.onBufferFill(col, row, box.width, box.height, c) } } - override def getBackgroundColor = data.background.value - - override def isBackgroundFromPalette = data.background.isPalette - - def copy(col: Int, row: Int, w: Int, h: Int, tx: Int, ty: Int) = - if (data.copy(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.onBufferFill(col, row, w, h, c) - - 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) + private def truncateToViewport(col: Int, row: Int, s: String, vertical: Boolean): String = { + var start: Int = 0 + var last: Int = -1 + if (vertical) { + val box = truncateToViewport(col, row, col, row + s.length - 1) + if (box.isVisible) { + start = box.y1 - row + last = box.y2 - row + } + } else { + val box = truncateToViewport(col, row, col + s.length - 1, row) + if (box.isVisible) { + start = box.x1 - col + last = box.x2 - col + } } - def get(col: Int, row: Int) = data.get(col, row) - - override def getForegroundColor(column: Int, row: Int) = - if (isForegroundFromPalette(column, row)) { - PackedColor.extractForeground(color(column, row)) - } - else { - PackedColor.unpackForeground(color(column, row), data.format) + if (last >= start && start >= 0 && last < s.length) { + s.substring(start, last + 1) + } else { + "" } + } - override def isForegroundFromPalette(column: Int, row: Int) = - data.format.isFromPalette(PackedColor.extractForeground(color(column, row))) - - override def getBackgroundColor(column: Int, row: Int) = - if (isBackgroundFromPalette(column, row)) { - PackedColor.extractBackground(color(column, row)) + override def onBufferSet(col: Int, row: Int, s: String, vertical: Boolean): Unit = { + // only notify about viewport changes + val truncatedString = truncateToViewport(col, row, s, vertical) + if (!truncatedString.isEmpty) { + proxy.onBufferSet(col, row, truncatedString, vertical) } - else { - PackedColor.unpackBackground(color(column, row), data.format) - } - - 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) - } + super.rawSetText(col, row, text) 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 - } - } + super.rawSetBackground(col, row, color) // Better for bandwidth to send packed shorts here. Would need a special case for handling on client, // though, so let's be wasteful for once... 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 - } - } + super.rawSetForeground(col, row, color) // Better for bandwidth to send packed shorts here. Would need a special case for handling on client, // though, so let's be wasteful for once... proxy.onBufferRawSetForeground(col, row, color) } - private def color(column: Int, row: Int) = { - if (column < 0 || column >= getWidth || row < 0 || row >= getHeight) - throw new IndexOutOfBoundsException() - else data.color(row)(column) - } + @SideOnly(Side.CLIENT) + override def renderText: Boolean = relativeLitArea != 0 && proxy.render() @SideOnly(Side.CLIENT) - override def renderText() = relativeLitArea != 0 && proxy.render() + override def renderWidth: Int = TextBufferRenderCache.renderer.charRenderWidth * getViewportWidth @SideOnly(Side.CLIENT) - override def renderWidth = TextBufferRenderCache.renderer.charRenderWidth * getViewportWidth + override def renderHeight: Int = TextBufferRenderCache.renderer.charRenderHeight * getViewportHeight @SideOnly(Side.CLIENT) - override def renderHeight = TextBufferRenderCache.renderer.charRenderHeight * getViewportHeight + override def setRenderingEnabled(enabled: Boolean): Unit = isRendering = enabled @SideOnly(Side.CLIENT) - override def setRenderingEnabled(enabled: Boolean) = isRendering = enabled + override def isRenderingEnabled: Boolean = isRendering - @SideOnly(Side.CLIENT) - override def isRenderingEnabled = isRendering - - override def keyDown(character: Char, code: Int, player: EntityPlayer) = + override def keyDown(character: Char, code: Int, player: EntityPlayer): Unit = proxy.keyDown(character, code, player) - override def keyUp(character: Char, code: Int, player: EntityPlayer) = + override def keyUp(character: Char, code: Int, player: EntityPlayer): Unit = proxy.keyUp(character, code, player) - override def clipboard(value: String, player: EntityPlayer) = + override def clipboard(value: String, player: EntityPlayer): Unit = proxy.clipboard(value, player) - override def mouseDown(x: Double, y: Double, button: Int, player: EntityPlayer) = + override def mouseDown(x: Double, y: Double, button: Int, player: EntityPlayer): Unit = proxy.mouseDown(x, y, button, player) - override def mouseDrag(x: Double, y: Double, button: Int, player: EntityPlayer) = + override def mouseDrag(x: Double, y: Double, button: Int, player: EntityPlayer): Unit = proxy.mouseDrag(x, y, button, player) - override def mouseUp(x: Double, y: Double, button: Int, player: EntityPlayer) = + override def mouseUp(x: Double, y: Double, button: Int, player: EntityPlayer): Unit = proxy.mouseUp(x, y, button, player) - override def mouseScroll(x: Double, y: Double, delta: Int, player: EntityPlayer) = + override def mouseScroll(x: Double, y: Double, delta: Int, player: EntityPlayer): Unit = proxy.mouseScroll(x, y, delta, player) def copyToAnalyzer(line: Int, player: EntityPlayer): Unit = { @@ -542,7 +490,7 @@ class TextBuffer(val host: EnvironmentHost) extends prefab.ManagedEnvironment wi } // Null check for Waila (and other mods that may call this client side). - override def save(nbt: NBTTagCompound) = if (node != null) { + override def save(nbt: NBTTagCompound): Unit = if (node != null) { super.save(nbt) // Happy thread synchronization hack! Here's the problem: GPUs allow direct // calls for modifying screens to give a more responsive experience. This @@ -930,4 +878,4 @@ object TextBuffer { } } -} \ No newline at end of file +} diff --git a/src/main/scala/li/cil/oc/common/component/traits/TextBufferProxy.scala b/src/main/scala/li/cil/oc/common/component/traits/TextBufferProxy.scala new file mode 100644 index 000000000..ae55093ff --- /dev/null +++ b/src/main/scala/li/cil/oc/common/component/traits/TextBufferProxy.scala @@ -0,0 +1,154 @@ +package li.cil.oc.common.component.traits + +import li.cil.oc.util +import li.cil.oc.api +import li.cil.oc.api.internal.TextBuffer +import li.cil.oc.util.PackedColor + +trait TextBufferProxy extends api.internal.TextBuffer { + def data: util.TextBuffer + + override def getWidth: Int = data.width + + override def getHeight: Int = data.height + + override def setColorDepth(depth: api.internal.TextBuffer.ColorDepth): Boolean = { + if (depth.ordinal > getMaximumColorDepth.ordinal) + throw new IllegalArgumentException("unsupported depth") + data.format = PackedColor.Depth.format(depth) + } + + override def getColorDepth: TextBuffer.ColorDepth = data.format.depth + + override def setPaletteColor(index: Int, color: Int): Unit = data.format match { + case palette: PackedColor.MutablePaletteFormat => + palette(index) = color + case _ => throw new Exception("palette not available") + } + + override def getPaletteColor(index: Int): Int = data.format match { + case palette: PackedColor.MutablePaletteFormat => palette(index) + case _ => throw new Exception("palette not available") + } + + def onBufferColorChange(): Unit = {} + + override def setForegroundColor(color: Int): Unit = setForegroundColor(color, isFromPalette = false) + + override def setForegroundColor(color: Int, isFromPalette: Boolean) { + val value = PackedColor.Color(color, isFromPalette) + if (data.foreground != value) { + data.foreground = value + onBufferColorChange() + } + } + + override def getForegroundColor: Int = data.foreground.value + + override def isForegroundFromPalette: Boolean = data.foreground.isPalette + + override def setBackgroundColor(color: Int): Unit = setBackgroundColor(color, isFromPalette = false) + + override def setBackgroundColor(color: Int, isFromPalette: Boolean) { + val value = PackedColor.Color(color, isFromPalette) + if (data.background != value) { + data.background = value + onBufferColorChange() + } + } + + override def getBackgroundColor: Int = data.background.value + + override def isBackgroundFromPalette: Boolean = data.background.isPalette + + def onBufferCopy(col: Int, row: Int, w: Int, h: Int, tx: Int, ty: Int): Unit = {} + + def copy(col: Int, row: Int, w: Int, h: Int, tx: Int, ty: Int): Unit = + if (data.copy(col, row, w, h, tx, ty)) + onBufferCopy(col, row, w, h, tx, ty) + + def onBufferFill(col: Int, row: Int, w: Int, h: Int, c: Char): Unit = {} + + def fill(col: Int, row: Int, w: Int, h: Int, c: Char): Unit = + if (data.fill(col, row, w, h, c)) + onBufferFill(col, row, w, h, c) + + def onBufferSet(col: Int, row: Int, s: String, vertical: Boolean): Unit = {} + + 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)) + onBufferSet(x, row, truncated, vertical) + } + + def get(col: Int, row: Int): Char = data.get(col, row) + + override def getForegroundColor(column: Int, row: Int): Int = + if (isForegroundFromPalette(column, row)) { + PackedColor.extractForeground(color(column, row)) + } + else { + PackedColor.unpackForeground(color(column, row), data.format) + } + + override def isForegroundFromPalette(column: Int, row: Int): Boolean = + data.format.isFromPalette(PackedColor.extractForeground(color(column, row))) + + override def getBackgroundColor(column: Int, row: Int): Int = + if (isBackgroundFromPalette(column, row)) { + PackedColor.extractBackground(color(column, row)) + } + else { + PackedColor.unpackBackground(color(column, row), data.format) + } + + override def isBackgroundFromPalette(column: Int, row: Int): Boolean = + 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) + } + } + + 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 + } + } + } + + 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 + } + } + } + + private def color(column: Int, row: Int): Short = { + if (column < 0 || column >= getWidth || row < 0 || row >= getHeight) + throw new IndexOutOfBoundsException() + else data.color(row)(column) + } +} +