blob: 7f62ac713c055b7bc52d986a8fdc4d0ee229faec [file] [log] [blame] [raw]
package li.cil.oc.common.component
import li.cil.oc.api.network.{Message, Node, Visibility}
import li.cil.oc.common.component
import li.cil.oc.common.tileentity
import li.cil.oc.server.{PacketSender => ServerPacketSender}
import li.cil.oc.util.{Persistable, PackedColor, TextBuffer}
import li.cil.oc.{api, Config}
import net.minecraft.nbt.NBTTagCompound
import scala.collection.convert.WrapAsScala._
class Buffer(val owner: Buffer.Environment) extends api.network.Environment with Persistable {
val node = api.Network.newNode(this, Visibility.Network).
withComponent("screen").
withConnector().
create()
val buffer = new TextBuffer(maxResolution, maxDepth)
def maxResolution = Config.screenResolutionsByTier(owner.tier)
def maxDepth = Config.screenDepthsByTier(owner.tier)
// ----------------------------------------------------------------------- //
def text = buffer.toString
def lines = buffer.buffer
def color = buffer.color
// ----------------------------------------------------------------------- //
def depth = buffer.depth
def depth_=(value: PackedColor.Depth.Value) = {
if (value > maxDepth)
throw new IllegalArgumentException("unsupported depth")
if (buffer.depth = value) {
owner.onScreenDepthChange(value)
true
}
else false
}
def foreground = buffer.foreground
def foreground_=(value: Int) = {
if (buffer.foreground != value) {
val result = buffer.foreground
buffer.foreground = value
owner.onScreenColorChange(foreground, background)
result
}
else value
}
def background = buffer.background
def background_=(value: Int) = {
if (buffer.background != value) {
val result = buffer.background
buffer.background = value
owner.onScreenColorChange(foreground, background)
result
}
else value
}
def resolution = buffer.size
def resolution_=(value: (Int, Int)) = {
val (w, h) = value
val (mw, mh) = maxResolution
if (w < 1 || w > mw || h < 1 || h > mh)
throw new IllegalArgumentException("unsupported resolution")
if (buffer.size = value) {
if (node != null) {
node.sendToReachable("computer.signal", "screen_resized", Int.box(w), Int.box(h))
}
owner.onScreenResolutionChange(w, h)
true
}
else false
}
def get(col: Int, row: Int) = buffer.get(col, row)
def set(col: Int, row: Int, s: String) = if (col < buffer.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, truncated) =
if (col < 0) (0, s.substring(-col))
else (col, s.substring(0, s.length min (buffer.width - col)))
if (buffer.set(x, row, truncated))
owner.onScreenSet(x, row, truncated)
}
def fill(col: Int, row: Int, w: Int, h: Int, c: Char) =
if (buffer.fill(col, row, w, h, c))
owner.onScreenFill(col, row, w, h, c)
def copy(col: Int, row: Int, w: Int, h: Int, tx: Int, ty: Int) =
if (buffer.copy(col, row, w, h, tx, ty))
owner.onScreenCopy(col, row, w, h, tx, ty)
// ----------------------------------------------------------------------- //
def onConnect(node: Node) {}
def onDisconnect(node: Node) {}
def onMessage(message: Message) {}
// ----------------------------------------------------------------------- //
override def load(nbt: NBTTagCompound) = {
buffer.load(nbt.getCompoundTag(Config.namespace + "buffer"))
}
override def save(nbt: NBTTagCompound) = {
// Happy thread synchronization hack! Here's the problem: GPUs allow direct
// calls for modifying screens to give a more responsive experience. This
// causes the following problem: when saving, if the screen is saved first,
// then the executor runs in parallel and changes the screen *before* the
// server thread begins saving that computer, the saved computer will think
// it changed the screen, although the saved screen wasn't. To avoid that we
// wait for all computers the screen is connected to to finish their current
// execution and pausing them (which will make them resume in the next tick
// when their update() runs).
if (node.network != null) {
for (node <- node.reachableNodes) node.host match {
case computer: tileentity.Computer => computer.computer.pause()
case _ =>
}
}
val screenNbt = new NBTTagCompound
buffer.save(screenNbt)
nbt.setCompoundTag(Config.namespace + "buffer", screenNbt)
}
}
object Buffer {
trait Environment extends tileentity.Environment with Persistable {
val buffer = new component.Buffer(this)
var bufferIsDirty = false
def tier: Int
// ----------------------------------------------------------------------- //
override def load(nbt: NBTTagCompound) = {
super.load(nbt)
buffer.load(nbt)
}
override def save(nbt: NBTTagCompound) = {
super.save(nbt)
buffer.save(nbt)
}
// ----------------------------------------------------------------------- //
override def onConnect(node: Node) {
super.onConnect(node)
if (node == this.node) {
node.connect(buffer.node)
}
}
override def onDisconnect(node: Node) {
super.onDisconnect(node)
if (node == this.node) {
buffer.node.remove()
}
}
// ----------------------------------------------------------------------- //
def onScreenColorChange(foreground: Int, background: Int) {
if (isServer) {
world.markTileEntityChunkModified(x, y, z, this.asInstanceOf[net.minecraft.tileentity.TileEntity])
ServerPacketSender.sendScreenColorChange(this, foreground, background)
}
}
def onScreenCopy(col: Int, row: Int, w: Int, h: Int, tx: Int, ty: Int) {
if (isServer) {
world.markTileEntityChunkModified(x, y, z, this.asInstanceOf[net.minecraft.tileentity.TileEntity])
ServerPacketSender.sendScreenCopy(this, col, row, w, h, tx, ty)
}
else markForRenderUpdate()
}
def onScreenDepthChange(depth: PackedColor.Depth.Value) {
if (isServer) {
world.markTileEntityChunkModified(x, y, z, this.asInstanceOf[net.minecraft.tileentity.TileEntity])
ServerPacketSender.sendScreenDepthChange(this, depth)
}
else markForRenderUpdate()
}
def onScreenFill(col: Int, row: Int, w: Int, h: Int, c: Char) {
if (isServer) {
world.markTileEntityChunkModified(x, y, z, this.asInstanceOf[net.minecraft.tileentity.TileEntity])
ServerPacketSender.sendScreenFill(this, col, row, w, h, c)
}
else markForRenderUpdate()
}
def onScreenResolutionChange(w: Int, h: Int) {
if (isServer) {
world.markTileEntityChunkModified(x, y, z, this.asInstanceOf[net.minecraft.tileentity.TileEntity])
ServerPacketSender.sendScreenResolutionChange(this, w, h)
}
else markForRenderUpdate()
}
def onScreenSet(col: Int, row: Int, s: String) {
if (isServer) {
world.markTileEntityChunkModified(x, y, z, this.asInstanceOf[net.minecraft.tileentity.TileEntity])
ServerPacketSender.sendScreenSet(this, col, row, s)
}
else markForRenderUpdate()
}
protected def markForRenderUpdate() {
bufferIsDirty = true
}
}
}