| 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 |
| } |
| } |
| |
| } |