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