|  | package li.cil.oc.server.component | 
|  |  | 
|  | import li.cil.oc.api.Network | 
|  | import li.cil.oc.api.component.TextBuffer | 
|  | import li.cil.oc.api.component.TextBuffer.ColorDepth | 
|  | import li.cil.oc.api.network._ | 
|  | import li.cil.oc.common.component | 
|  | import li.cil.oc.util.PackedColor | 
|  | import li.cil.oc.{Localization, Settings} | 
|  | import net.minecraft.nbt.NBTTagCompound | 
|  |  | 
|  | abstract class GraphicsCard extends component.ManagedComponent { | 
|  | val node = Network.newNode(this, Visibility.Neighbors). | 
|  | withComponent("gpu"). | 
|  | withConnector(). | 
|  | create() | 
|  |  | 
|  | protected val maxResolution: (Int, Int) | 
|  |  | 
|  | protected val maxDepth: ColorDepth | 
|  |  | 
|  | private var screenAddress: Option[String] = None | 
|  |  | 
|  | private var screenInstance: Option[TextBuffer] = None | 
|  |  | 
|  | private def screen(f: (TextBuffer) => Array[AnyRef]) = screenInstance match { | 
|  | case Some(screen) => screen.synchronized(f(screen)) | 
|  | case _ => Array(Unit, "no screen") | 
|  | } | 
|  |  | 
|  | // ----------------------------------------------------------------------- // | 
|  |  | 
|  | override val canUpdate = true | 
|  |  | 
|  | override def update() { | 
|  | super.update() | 
|  | if (node.network != null && screenInstance.isEmpty && screenAddress.isDefined) { | 
|  | Option(node.network.node(screenAddress.get)) match { | 
|  | case Some(node: Node) if node.host.isInstanceOf[TextBuffer] => | 
|  | screenInstance = Some(node.host.asInstanceOf[TextBuffer]) | 
|  | case _ => | 
|  | // This could theoretically happen after loading an old address, but | 
|  | // if the screen either disappeared between saving and now or changed | 
|  | // type. The first scenario is more likely, and could happen if the | 
|  | // chunk the screen is in isn't loaded when the chunk the GPU is in | 
|  | // gets loaded. | 
|  | screenAddress = None | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | @Callback | 
|  | def bind(context: Context, args: Arguments): Array[AnyRef] = { | 
|  | val address = args.checkString(0) | 
|  | node.network.node(address) match { | 
|  | case null => result(Unit, "invalid address") | 
|  | case node: Node if node.host.isInstanceOf[TextBuffer] => | 
|  | screenAddress = Option(address) | 
|  | screenInstance = Some(node.host.asInstanceOf[TextBuffer]) | 
|  | screen(s => { | 
|  | val (gmw, gmh) = maxResolution | 
|  | val smw = s.getMaximumWidth | 
|  | val smh = s.getMaximumHeight | 
|  | s.setResolution(math.min(gmw, smw), math.min(gmh, smh)) | 
|  | s.setColorDepth(ColorDepth.values.apply(math.min(maxDepth.ordinal, s.getMaximumColorDepth.ordinal))) | 
|  | s.setForegroundColor(0xFFFFFF) | 
|  | s.setBackgroundColor(0x000000) | 
|  | result(true) | 
|  | }) | 
|  | case _ => result(Unit, "not a screen") | 
|  | } | 
|  | } | 
|  |  | 
|  | @Callback(direct = true) | 
|  | def getBackground(context: Context, args: Arguments): Array[AnyRef] = | 
|  | screen(s => result(s.getBackgroundColor, s.isBackgroundFromPalette)) | 
|  |  | 
|  | def setBackground(context: Context, args: Arguments): Array[AnyRef] = { | 
|  | val color = args.checkInteger(0) | 
|  | screen(s => { | 
|  | val oldValue = s.getBackgroundColor | 
|  | val (oldColor, oldIndex) = | 
|  | if (s.isBackgroundFromPalette) { | 
|  | (s.getPaletteColor(oldValue), oldValue) | 
|  | } | 
|  | else { | 
|  | (oldValue, Unit) | 
|  | } | 
|  | s.setBackgroundColor(color, args.count > 1 && args.checkBoolean(1)) | 
|  | result(oldColor, oldIndex) | 
|  | }) | 
|  | } | 
|  |  | 
|  | @Callback(direct = true) | 
|  | def getForeground(context: Context, args: Arguments): Array[AnyRef] = | 
|  | screen(s => result(s.getForegroundColor, s.isForegroundFromPalette)) | 
|  |  | 
|  | def setForeground(context: Context, args: Arguments): Array[AnyRef] = { | 
|  | val color = args.checkInteger(0) | 
|  | screen(s => { | 
|  | val oldValue = s.getForegroundColor | 
|  | val (oldColor, oldIndex) = | 
|  | if (s.isForegroundFromPalette) { | 
|  | (s.getPaletteColor(oldValue), oldValue) | 
|  | } | 
|  | else { | 
|  | (oldValue, Unit) | 
|  | } | 
|  | s.setForegroundColor(color, args.count > 1 && args.checkBoolean(1)) | 
|  | result(oldColor, oldIndex) | 
|  | }) | 
|  | } | 
|  |  | 
|  | @Callback(direct = true) | 
|  | def getPaletteColor(context: Context, args: Arguments): Array[AnyRef] = { | 
|  | val index = args.checkInteger(0) | 
|  | screen(s => try result(s.getPaletteColor(index)) catch { | 
|  | case _: ArrayIndexOutOfBoundsException => throw new IllegalArgumentException("invalid palette index") | 
|  | }) | 
|  | } | 
|  |  | 
|  | @Callback | 
|  | def setPaletteColor(context: Context, args: Arguments): Array[AnyRef] = { | 
|  | val index = args.checkInteger(0) | 
|  | val color = args.checkInteger(1) | 
|  | context.pause(0.1) | 
|  | screen(s => try { | 
|  | val oldColor = s.getPaletteColor(index) | 
|  | s.setPaletteColor(index, color) | 
|  | result(oldColor) | 
|  | } | 
|  | catch { | 
|  | case _: ArrayIndexOutOfBoundsException => throw new IllegalArgumentException("invalid palette index") | 
|  | }) | 
|  | } | 
|  |  | 
|  | @Callback(direct = true) | 
|  | def getDepth(context: Context, args: Arguments): Array[AnyRef] = | 
|  | screen(s => result(PackedColor.Depth.bits(s.getColorDepth))) | 
|  |  | 
|  | @Callback | 
|  | def setDepth(context: Context, args: Arguments): Array[AnyRef] = { | 
|  | val depth = args.checkInteger(0) | 
|  | screen(s => { | 
|  | val oldDepth = s.getColorDepth | 
|  | depth match { | 
|  | case 1 => s.setColorDepth(ColorDepth.OneBit) | 
|  | case 4 if maxDepth.ordinal >= ColorDepth.FourBit.ordinal => s.setColorDepth(ColorDepth.FourBit) | 
|  | case 8 if maxDepth.ordinal >= ColorDepth.EightBit.ordinal => s.setColorDepth(ColorDepth.EightBit) | 
|  | case _ => throw new IllegalArgumentException("unsupported depth") | 
|  | } | 
|  | result(oldDepth) | 
|  | }) | 
|  | } | 
|  |  | 
|  | @Callback(direct = true) | 
|  | def maxDepth(context: Context, args: Arguments): Array[AnyRef] = | 
|  | screen(s => result(PackedColor.Depth.bits(ColorDepth.values.apply(math.min(maxDepth.ordinal, s.getMaximumColorDepth.ordinal))))) | 
|  |  | 
|  | @Callback(direct = true) | 
|  | def getResolution(context: Context, args: Arguments): Array[AnyRef] = | 
|  | screen(s => result(s.getWidth, s.getHeight)) | 
|  |  | 
|  | @Callback | 
|  | def setResolution(context: Context, args: Arguments): Array[AnyRef] = { | 
|  | val w = args.checkInteger(0) | 
|  | val h = args.checkInteger(1) | 
|  | val (mw, mh) = maxResolution | 
|  | // Even though the buffer itself checks this again, we need this here for | 
|  | // the minimum of screen and GPU resolution. | 
|  | if (w < 1 || h < 1 || w > mw || h > mw || h * w > mw * mh) | 
|  | throw new IllegalArgumentException("unsupported resolution") | 
|  | screen(s => result(s.setResolution(w, h))) | 
|  | } | 
|  |  | 
|  | @Callback(direct = true) | 
|  | def maxResolution(context: Context, args: Arguments): Array[AnyRef] = | 
|  | screen(s => { | 
|  | val (gmw, gmh) = maxResolution | 
|  | val smw = s.getMaximumWidth | 
|  | val smh = s.getMaximumHeight | 
|  | result(math.min(gmw, smw), math.min(gmh, smh)) | 
|  | }) | 
|  |  | 
|  | @Callback(direct = true) | 
|  | def get(context: Context, args: Arguments): Array[AnyRef] = { | 
|  | val x = args.checkInteger(0) - 1 | 
|  | val y = args.checkInteger(1) - 1 | 
|  | screen(s => { | 
|  | val fgValue = s.getForegroundColor(x, y) | 
|  | val (fgColor, fgIndex) = | 
|  | if (s.isForegroundFromPalette(x, y)) { | 
|  | (s.getPaletteColor(fgValue), fgValue) | 
|  | } | 
|  | else { | 
|  | (fgValue, Unit) | 
|  | } | 
|  |  | 
|  | val bgValue = s.getBackgroundColor(x, y) | 
|  | val (bgColor, bgIndex) = | 
|  | if (s.isBackgroundFromPalette(x, y)) { | 
|  | (s.getPaletteColor(bgValue), bgValue) | 
|  | } | 
|  | else { | 
|  | (bgValue, Unit) | 
|  | } | 
|  |  | 
|  | result(s.get(x, y), fgColor, bgColor, fgIndex, bgIndex) | 
|  | }) | 
|  | } | 
|  |  | 
|  | def set(context: Context, args: Arguments): Array[AnyRef] = { | 
|  | val x = args.checkInteger(0) - 1 | 
|  | val y = args.checkInteger(1) - 1 | 
|  | val value = args.checkString(2) | 
|  | val vertical = args.count > 3 && args.checkBoolean(3) | 
|  |  | 
|  | screen(s => { | 
|  | if (consumePower(value.length, Settings.get.gpuSetCost)) { | 
|  | s.set(x, y, value, vertical) | 
|  | result(true) | 
|  | } | 
|  | else result(Unit, "not enough energy") | 
|  | }) | 
|  | } | 
|  |  | 
|  | def copy(context: Context, args: Arguments): Array[AnyRef] = { | 
|  | val x = args.checkInteger(0) - 1 | 
|  | val y = args.checkInteger(1) - 1 | 
|  | val w = args.checkInteger(2) | 
|  | val h = args.checkInteger(3) | 
|  | val tx = args.checkInteger(4) | 
|  | val ty = args.checkInteger(5) | 
|  | screen(s => { | 
|  | if (consumePower(w * h, Settings.get.gpuCopyCost)) { | 
|  | s.copy(x, y, w, h, tx, ty) | 
|  | result(true) | 
|  | } | 
|  | else result(Unit, "not enough energy") | 
|  | }) | 
|  | } | 
|  |  | 
|  | def fill(context: Context, args: Arguments): Array[AnyRef] = { | 
|  | val x = args.checkInteger(0) - 1 | 
|  | val y = args.checkInteger(1) - 1 | 
|  | val w = args.checkInteger(2) | 
|  | val h = args.checkInteger(3) | 
|  | val value = args.checkString(4) | 
|  | if (value.length == 1) screen(s => { | 
|  | val c = value.charAt(0) | 
|  | val cost = if (c == ' ') Settings.get.gpuClearCost else Settings.get.gpuFillCost | 
|  | if (consumePower(w * h, cost)) { | 
|  | s.fill(x, y, w, h, value.charAt(0)) | 
|  | result(true) | 
|  | } | 
|  | else { | 
|  | result(Unit, "not enough energy") | 
|  | } | 
|  | }) | 
|  | else throw new Exception("invalid fill value") | 
|  | } | 
|  |  | 
|  | private def consumePower(n: Double, cost: Double) = node.tryChangeBuffer(-n * cost) | 
|  |  | 
|  | // ----------------------------------------------------------------------- // | 
|  |  | 
|  | override def onMessage(message: Message) { | 
|  | super.onMessage(message) | 
|  | if (message.name == "computer.stopped" && node.isNeighborOf(message.source)) { | 
|  | screen(s => { | 
|  | val (gmw, gmh) = maxResolution | 
|  | val smw = s.getMaximumWidth | 
|  | val smh = s.getMaximumHeight | 
|  | s.setResolution(math.min(gmw, smw), math.min(gmh, smh)) | 
|  | s.setColorDepth(ColorDepth.values.apply(math.min(maxDepth.ordinal, s.getMaximumColorDepth.ordinal))) | 
|  | s.setForegroundColor(0xFFFFFF) | 
|  | val w = s.getWidth | 
|  | val h = s.getHeight | 
|  | message.source.host match { | 
|  | case machine: machine.Machine if machine.lastError != null => | 
|  | if (s.getColorDepth.ordinal > ColorDepth.OneBit.ordinal) s.setBackgroundColor(0x0000FF) | 
|  | else s.setBackgroundColor(0x000000) | 
|  | s.fill(0, 0, w, h, ' ') | 
|  | try { | 
|  | val wrapRegEx = s"(.{1,${math.max(1, w - 2)}})\\s".r | 
|  | val lines = wrapRegEx.replaceAllIn(Localization.localizeImmediately(machine.lastError).replace("\t", "  ") + "\n", m => m.group(1) + "\n").lines.toArray | 
|  | val firstRow = ((h - lines.length) / 2) max 2 | 
|  |  | 
|  | val message = "Unrecoverable Error" | 
|  | s.set((w - message.length) / 2, firstRow - 2, message, false) | 
|  |  | 
|  | val maxLineLength = lines.map(_.length).max | 
|  | val col = ((w - maxLineLength) / 2) max 0 | 
|  | for ((line, idx) <- lines.zipWithIndex) { | 
|  | val row = firstRow + idx | 
|  | s.set(col, row, line, false) | 
|  | } | 
|  | } | 
|  | catch { | 
|  | case t: Throwable => t.printStackTrace() | 
|  | } | 
|  | case _ => | 
|  | s.setBackgroundColor(0x000000) | 
|  | s.fill(0, 0, w, h, ' ') | 
|  | } | 
|  | null // For screen() | 
|  | }) | 
|  | } | 
|  | } | 
|  |  | 
|  | override def onDisconnect(node: Node) { | 
|  | super.onDisconnect(node) | 
|  | if (node == this.node || screenAddress.exists(_ == node.address)) { | 
|  | screenAddress = None | 
|  | screenInstance = None | 
|  | } | 
|  | } | 
|  |  | 
|  | // ----------------------------------------------------------------------- // | 
|  |  | 
|  | override def load(nbt: NBTTagCompound) { | 
|  | super.load(nbt) | 
|  |  | 
|  | if (nbt.hasKey("screen")) { | 
|  | nbt.getString("screen") match { | 
|  | case screen: String if !screen.isEmpty => screenAddress = Some(screen) | 
|  | case _ => screenAddress = None | 
|  | } | 
|  | screenInstance = None | 
|  | } | 
|  | } | 
|  |  | 
|  | override def save(nbt: NBTTagCompound) { | 
|  | super.save(nbt) | 
|  |  | 
|  | if (screenAddress.isDefined) { | 
|  | nbt.setString("screen", screenAddress.get) | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | object GraphicsCard { | 
|  |  | 
|  | // IMPORTANT: usually methods with side effects should *not* be direct | 
|  | // callbacks to avoid the massive headache synchronizing them ensues, in | 
|  | // particular when it comes to world saving. I'm making an exception for | 
|  | // screens, though since they'd be painfully sluggish otherwise. This also | 
|  | // means we have to use a somewhat nasty trick in common.component.Buffer's | 
|  | // save function: we wait for all computers in the same network to finish | 
|  | // their current execution and then pause them, to ensure the state of the | 
|  | // buffer is "clean", meaning the computer has the correct state when it is | 
|  | // saved in turn. If we didn't, a computer might change a screen after it was | 
|  | // saved, but before the computer was saved, leading to mismatching states in | 
|  | // the save file - a Bad Thing (TM). | 
|  |  | 
|  | class Tier1 extends GraphicsCard { | 
|  | protected val maxDepth = Settings.screenDepthsByTier(0) | 
|  | protected val maxResolution = Settings.screenResolutionsByTier(0) | 
|  |  | 
|  | @Callback(direct = true, limit = 1) | 
|  | override def copy(context: Context, args: Arguments) = super.copy(context, args) | 
|  |  | 
|  | @Callback(direct = true, limit = 1) | 
|  | override def fill(context: Context, args: Arguments) = super.fill(context, args) | 
|  |  | 
|  | @Callback(direct = true, limit = 4) | 
|  | override def set(context: Context, args: Arguments) = super.set(context, args) | 
|  |  | 
|  | @Callback(direct = true, limit = 2) | 
|  | override def setBackground(context: Context, args: Arguments) = super.setBackground(context, args) | 
|  |  | 
|  | @Callback(direct = true, limit = 2) | 
|  | override def setForeground(context: Context, args: Arguments) = super.setForeground(context, args) | 
|  | } | 
|  |  | 
|  | class Tier2 extends GraphicsCard { | 
|  | protected val maxDepth = Settings.screenDepthsByTier(1) | 
|  | protected val maxResolution = Settings.screenResolutionsByTier(1) | 
|  |  | 
|  | @Callback(direct = true, limit = 2) | 
|  | override def copy(context: Context, args: Arguments) = super.copy(context, args) | 
|  |  | 
|  | @Callback(direct = true, limit = 4) | 
|  | override def fill(context: Context, args: Arguments) = super.fill(context, args) | 
|  |  | 
|  | @Callback(direct = true, limit = 8) | 
|  | override def set(context: Context, args: Arguments) = super.set(context, args) | 
|  |  | 
|  | @Callback(direct = true, limit = 4) | 
|  | override def setBackground(context: Context, args: Arguments) = super.setBackground(context, args) | 
|  |  | 
|  | @Callback(direct = true, limit = 4) | 
|  | override def setForeground(context: Context, args: Arguments) = super.setForeground(context, args) | 
|  | } | 
|  |  | 
|  | class Tier3 extends GraphicsCard { | 
|  | protected val maxDepth = Settings.screenDepthsByTier(2) | 
|  | protected val maxResolution = Settings.screenResolutionsByTier(2) | 
|  |  | 
|  | @Callback(direct = true, limit = 4) | 
|  | override def copy(context: Context, args: Arguments) = super.copy(context, args) | 
|  |  | 
|  | @Callback(direct = true, limit = 8) | 
|  | override def fill(context: Context, args: Arguments) = super.fill(context, args) | 
|  |  | 
|  | @Callback(direct = true, limit = 16) | 
|  | override def set(context: Context, args: Arguments) = super.set(context, args) | 
|  |  | 
|  | @Callback(direct = true, limit = 8) | 
|  | override def setBackground(context: Context, args: Arguments) = super.setBackground(context, args) | 
|  |  | 
|  | @Callback(direct = true, limit = 8) | 
|  | override def setForeground(context: Context, args: Arguments) = super.setForeground(context, args) | 
|  | } | 
|  |  | 
|  | } |