blob: bf5221c6b411f09769695de7c67e69dfe1ea9640 [file] [log] [blame] [raw]
package li.cil.oc.common.util
import net.minecraft.nbt.NBTTagCompound
import net.minecraft.nbt.NBTTagList
import net.minecraft.nbt.NBTTagString
/**
* This stores chars in a 2D-Array and provides some manipulation functions.
*
* The main purpose of this is to allow moving most implementation detail to
* the Lua side while keeping bandwidth costs low and still allowing for
* relatively fast updates, given a smart algorithm (using copy()/fill()
* instead of set()ing everything).
*/
class TextBuffer(var width: Int, var height: Int) {
var buffer = Array.fill(height, width)(' ')
/** The current buffer size in columns by rows. */
def size = (width, height)
/**
* Set the new buffer size, returns true if the size changed.
*
* This will perform a proper resize as required, keeping as much of the
* buffer valid as possible if the size decreases, i.e. only data outside the
* new buffer size will be truncated, all data still inside will be copied.
*/
def size_=(value: (Int, Int)): Boolean = if (size != value) {
val (w, h) = value
val nbuffer = Array.fill(h, w)(' ')
(0 until (h min height)) foreach {
y => Array.copy(buffer(y), 0, nbuffer(y), 0, w min width)
}
buffer = nbuffer
width = w
height = h
true
}
else false
/**
* String based fill starting at a specified location.
*
* Note that this will ignore any newline characters to ensure only the
* specified row is changed and thereby limiting the size of the packet that
* has to be sent to clients to notify them of the change. In fact, this will
* ignore any and all control characters as according to Java's Character's
* 'isISOControl' property (0-31 and 127-159).
*/
def set(col: Int, row: Int, s: String): Boolean = {
var changed = false
for (i <- col until ((col + s.length) min width)) {
s(i - col) match {
case c if c.isControl => // Ignore.
case c =>
changed = changed || (buffer(row)(i) != c)
buffer(row)(i) = c
}
}
changed
}
/**
* Fills an area of the buffer with the specified character.
*
* Note that like set() this will ignore control characters (it will do
* nothing in that case).
*/
def fill(x: Int, y: Int, w: Int, h: Int, c: Char): Boolean =
if (c.isControl) false
else {
var changed = false
for (y <- (y max 0 min height) until ((y + h) max 0 min height))
for (x <- (x max 0 min width) until ((x + w) max 0 min width)) {
changed = changed || (buffer(y)(x) != c)
buffer(y)(x) = c
}
changed
}
/** Copies a portion of the buffer. */
def copy(col: Int, row: Int, w: Int, h: Int, tx: Int, ty: Int): Boolean = {
// Anything to do at all?
if (w <= 0 || h <= 0) return false
if (tx == 0 && ty == 0) return false
// Loop over the target rectangle, starting from the directions away from
// the source rectangle and copy the data. This way we ensure we don't
// overwrite anything we still need to copy.
val (dx0, dx1) = ((col + tx + w - 1) max 0 min (width - 1), (col + tx) max 0 min width) match {
case destx if tx > 0 => destx
case destx => destx.swap
}
val (dy0, dy1) = ((row + ty + h - 1) max 0 min (height - 1), (row + ty) max 0 min height) match {
case desty if (ty > 0) => desty
case desty => desty.swap
}
val (sx, sy) = ((if (tx > 0) -1 else 1), (if (ty > 0) -1 else 1))
// Copy values to destination rectangle if there source is valid.
var changed = false
for (ny <- dy0 to dy1 by sy) (ny - ty) match {
case oy if oy >= 0 && oy < height =>
for (nx <- dx0 to dx1 by sx) (nx - tx) match {
case ox if ox >= 0 && ox < width => {
changed = changed || (buffer(ny)(nx) != buffer(oy)(ox))
buffer(ny)(nx) = buffer(oy)(ox)
}
case _ => /* Got no source column. */
}
case _ => /* Got no source row. */
}
changed
}
def readFromNBT(nbt: NBTTagCompound): Unit = {
val w = nbt.getInteger("width")
val h = nbt.getInteger("height")
size = (w, h)
val b = nbt.getTagList("buffer")
for (i <- 0 until (h min b.tagCount())) {
set(0, i, b.tagAt(i).asInstanceOf[NBTTagString].data)
}
}
def writeToNBT(nbt: NBTTagCompound): Unit = {
nbt.setInteger("width", width)
nbt.setInteger("height", height)
val b = new NBTTagList
for (i <- 0 until height) {
b.appendTag(new NBTTagString("", String.valueOf(buffer(i))))
}
nbt.setTag("buffer", b)
}
def lines = buffer.map(String.valueOf(_))
override def toString = {
val b = StringBuilder.newBuilder
if (buffer.length > 0) {
b.appendAll(buffer(0))
for (y <- 1 until height) {
b.append('\n').appendAll(buffer(y))
}
}
b.toString
}
}