started working on redstone interfacing; improved term.write performance by caching the screen's resolution and added a signal for screen size changes

This commit is contained in:
Florian Nücke 2013-09-27 07:12:33 +02:00
parent 484dfd6a83
commit 12da32bc6c
20 changed files with 299 additions and 142 deletions

View File

@ -125,6 +125,7 @@ end)
--[[ Setup terminal API. ]] --[[ Setup terminal API. ]]
local idGpu, idScreen = 0, 0 local idGpu, idScreen = 0, 0
local screenWidth, screenHeight = 0, 0
local boundGpu = nil local boundGpu = nil
local cursorX, cursorY = 1, 1 local cursorX, cursorY = 1, 1
@ -157,22 +158,36 @@ event.listen("component_uninstalled", function(_, id)
end end
end) end)
event.listen("screen_resized", function(_, address, w, h)
local id = component.id(address)
if id == idScreen then
screenWidth = w
screenHeight = h
end
end)
term = {} term = {}
function term.gpu() function term.gpu()
return boundGpu return boundGpu
end end
function term.screenSize()
return screenWidth, screenHeight
end
local function bindIfPossible() local function bindIfPossible()
if idGpu > 0 and idScreen > 0 then if idGpu > 0 and idScreen > 0 then
if not boundGpu then if not boundGpu then
local function gpu() return component.address(idGpu) end local function gpu() return component.address(idGpu) end
local function screen() return component.address(idScreen) end local function screen() return component.address(idScreen) end
boundGpu = driver.gpu.bind(gpu, screen) boundGpu = driver.gpu.bind(gpu, screen)
screenWidth, screenHeight = boundGpu.getResolution()
event.fire("term_available") event.fire("term_available")
end end
elseif boundGpu then elseif boundGpu then
boundGpu = nil boundGpu = nil
screenWidth, screenHeight = 0, 0
event.fire("term_unavailable") event.fire("term_unavailable")
end end
end end
@ -208,18 +223,18 @@ end
function term.write(value, wrap) function term.write(value, wrap)
value = tostring(value) value = tostring(value)
local gpu = term.gpu() local w, h = screenWidth, screenHeight
if not gpu or value:len() == 0 then return end if value:len() == 0 or not boundGpu or w < 1 or h < 1 then
local w, h = gpu.getResolution() return
if not w or not h or w < 1 or h < 1 then return end end
local function checkCursor() local function checkCursor()
if cursorX > w then if cursorX > w then
cursorX = 1 cursorX = 1
cursorY = cursorY + 1 cursorY = cursorY + 1
end end
if cursorY > h then if cursorY > h then
gpu.copy(1, 1, w, h, 0, -1) boundGpu.copy(1, 1, w, h, 0, -1)
gpu.fill(1, h, w, 1, " ") boundGpu.fill(1, h, w, 1, " ")
cursorY = h cursorY = h
end end
end end
@ -227,12 +242,12 @@ function term.write(value, wrap)
while wrap and line:len() > w - cursorX + 1 do while wrap and line:len() > w - cursorX + 1 do
local partial = line:sub(1, w - cursorX + 1) local partial = line:sub(1, w - cursorX + 1)
line = line:sub(partial:len() + 1) line = line:sub(partial:len() + 1)
gpu.set(cursorX, cursorY, partial) boundGpu.set(cursorX, cursorY, partial)
cursorX = cursorX + partial:len() cursorX = cursorX + partial:len()
checkCursor() checkCursor()
end end
if line:len() > 0 then if line:len() > 0 then
gpu.set(cursorX, cursorY, line) boundGpu.set(cursorX, cursorY, line)
cursorX = cursorX + line:len() cursorX = cursorX + line:len()
end end
if nl:len() == 1 then if nl:len() == 1 then
@ -244,10 +259,8 @@ function term.write(value, wrap)
end end
function term.clear() function term.clear()
local gpu = term.gpu() if not boundGpu then return end
if not gpu then return end boundGpu.fill(1, 1, screenWidth, screenHeight, " ")
local w, h = gpu.getResolution()
gpu.fill(1, 1, w, h, " ")
cursorX, cursorY = 1, 1 cursorX, cursorY = 1, 1
end end
@ -332,7 +345,7 @@ while true do
if gpu then if gpu then
local x, y = term.getCursor() local x, y = term.getCursor()
if blinkState then if blinkState then
term.gpu().set(x, y, string.char(9608)) -- Solid block. term.gpu().set(x, y, string.char(0x2588)) -- Solid block.
else else
term.gpu().set(x, y, " ") term.gpu().set(x, y, " ")
end end

View File

@ -137,5 +137,5 @@ for k, v in pairs(driver.keyboard.keys) do
end end
function driver.keyboard.keys.isControl(char) function driver.keyboard.keys.isControl(char)
return char < 0x20 or (char >= 0x7F and char < 0x9F) return char < 0x20 or (char >= 0x7F and char <= 0x9F)
end end

View File

@ -0,0 +1,38 @@
--[[ API for Redstone Cards. ]]
driver.redstone = {}
driver.redstone.sides = {"top", "bottom", "left", "right", "front", "back"}
-- Add inverse mapping and aliases.
for k, v in pairs(sides) do sides[v] = k end
sides.up = sides.top
sides.down = sides.bottom
function driver.redstone.getAnalogInput(card, side)
checkArg(1, side, "number")
sendToNode(card, os.address(), "redstone.input", side)
end
function driver.redstone.getAnalogOutput(card, side)
checkArg(1, side, "number")
sendToNode(card, os.address(), "redstone.output", side)
end
function driver.redstone.setAnalogOutput(card, side, value)
checkArg(1, side, "number")
checkArg(2, side, "number")
sendToNode(card, os.address(), "redstone.output=", side, value)
end
function getInput(side)
return getAnalogInput(side) > 0
end
function getOutput(side)
return getAnalogOutput(side) > 0
end
function setOutput(side, value)
rs.setAnalogOutput(side, value and 15 or 0)
end

View File

@ -1,25 +0,0 @@
--[[
The Redstone API, provided by Redstone components.
]]
sides = {"top", "bottom", "left", "right", "front", "back"}
function getSides()
return sides
end
function getInput(side)
return getAnalogInput(side) > 0
end
function getOutput(side)
return getAnalogOutput(side) > 0
end
function setOutput(side, value)
rs.setAnalogOutput(side, value and 15 or 0)
end
getAnalogInput = _G.getAnalogInput
getAnalogOutput = _G.getAnalogOutput
setAnalogOutput = _G.setAnalogOutput

View File

@ -1,18 +1,20 @@
package li.cil.oc package li.cil.oc
import li.cil.oc.common.items.ItemGraphicsCard import li.cil.oc.common.items.{ItemRedstoneCard, ItemGraphicsCard, ItemHDD}
import li.cil.oc.common.items.ItemHDD
import li.cil.oc.common.util.ItemComponentCache import li.cil.oc.common.util.ItemComponentCache
import li.cil.oc.server.components.GraphicsCard import li.cil.oc.server.components.{RedstoneCard, GraphicsCard}
object Items { object Items {
var gpu: ItemGraphicsCard = null var gpu: ItemGraphicsCard = null
var hdd: ItemHDD = null var hdd: ItemHDD = null
var rs: ItemRedstoneCard = null
def init() { def init() {
gpu = new ItemGraphicsCard gpu = new ItemGraphicsCard
hdd = new ItemHDD hdd = new ItemHDD
rs = new ItemRedstoneCard
ItemComponentCache.register(gpu.itemID, nbt => new GraphicsCard(nbt)) ItemComponentCache.register(gpu.itemID, nbt => new GraphicsCard(nbt))
ItemComponentCache.register(rs.itemID, nbt => new RedstoneCard(nbt))
} }
} }

View File

@ -122,9 +122,9 @@ trait INetwork {
* <p/> * <p/>
* Messages should have a unique name to allow differentiating them when * Messages should have a unique name to allow differentiating them when
* handling them in a network node. For example, computers will try to parse * handling them in a network node. For example, computers will try to parse
* messages named "signal" by converting the message data to a signal and * messages named "computer.signal" by converting the message data to a
* inject that signal into the Lua VM, so no message not used for this purpose * signal and inject that signal into the Lua VM, so no message not used for
* should be named "signal". * this purpose should be named "computer.signal".
* <p/> * <p/>
* Note that message handlers may also return results. In this case that * Note that message handlers may also return results. In this case that
* result will be returned from this function. In the case that there are * result will be returned from this function. In the case that there are
@ -144,9 +144,9 @@ trait INetwork {
* <p/> * <p/>
* Messages should have a unique name to allow differentiating them when * Messages should have a unique name to allow differentiating them when
* handling them in a network node. For example, computers will try to parse * handling them in a network node. For example, computers will try to parse
* messages named "signal" by converting the message data to a signal and * messages named "computer.signal" by converting the message data to a
* inject that signal into the Lua VM, so no message not used for this purpose * signal and inject that signal into the Lua VM, so no message not used for
* should be named "signal". * this purpose should be named "computer.signal".
* *
* @param source the node that sends the message. * @param source the node that sends the message.
* @param data the message to send. * @param data the message to send.

View File

@ -50,11 +50,13 @@ trait IScreenEnvironment extends INetworkNode {
} }
def onScreenResolutionChange(w: Int, h: Int) def onScreenResolutionChange(w: Int, h: Int) = if (network != null) {
network.sendToAll(this, "computer.signal", "screen_resized", this.address, w, h)
}
def onScreenSet(col: Int, row: Int, s: String) def onScreenSet(col: Int, row: Int, s: String) {}
def onScreenFill(col: Int, row: Int, w: Int, h: Int, c: Char) def onScreenFill(col: Int, row: Int, w: Int, h: Int, c: Char) {}
def onScreenCopy(col: Int, row: Int, w: Int, h: Int, tx: Int, ty: Int) def onScreenCopy(col: Int, row: Int, w: Int, h: Int, tx: Int, ty: Int) {}
} }

View File

@ -8,7 +8,7 @@ import net.minecraft.world.World
class ItemGraphicsCard extends Item(Config.itemGPUId) { class ItemGraphicsCard extends Item(Config.itemGPUId) {
setMaxStackSize(1) setMaxStackSize(1)
//setHasSubtypes(true) //setHasSubtypes(true)
setUnlocalizedName("oc.gpu") setUnlocalizedName("Graphics Card")
setCreativeTab(CreativeTab) setCreativeTab(CreativeTab)
override def shouldPassSneakingClickToBlock(world: World, x: Int, y: Int, z: Int) = true override def shouldPassSneakingClickToBlock(world: World, x: Int, y: Int, z: Int) = true

View File

@ -0,0 +1,14 @@
package li.cil.oc.common.items
import li.cil.oc.{CreativeTab, Config}
import net.minecraft.item.Item
import net.minecraft.world.World
class ItemRedstoneCard extends Item(Config.itemGPUId + 1) {
setMaxStackSize(1)
//setHasSubtypes(true)
setUnlocalizedName("Redstone Card")
setCreativeTab(CreativeTab)
override def shouldPassSneakingClickToBlock(world: World, x: Int, y: Int, z: Int) = true
}

View File

@ -4,13 +4,15 @@ import java.util.concurrent.atomic.AtomicBoolean
import li.cil.oc.api.INetworkMessage import li.cil.oc.api.INetworkMessage
import li.cil.oc.client.computer.{Computer => ClientComputer} import li.cil.oc.client.computer.{Computer => ClientComputer}
import li.cil.oc.client.{PacketSender => ClientPacketSender} import li.cil.oc.client.{PacketSender => ClientPacketSender}
import li.cil.oc.server.components.RedstoneEnabled
import li.cil.oc.server.computer.IComputerEnvironment import li.cil.oc.server.computer.IComputerEnvironment
import li.cil.oc.server.computer.{Computer => ServerComputer} import li.cil.oc.server.computer.{Computer => ServerComputer}
import li.cil.oc.server.{PacketSender => ServerPacketSender} import li.cil.oc.server.{PacketSender => ServerPacketSender}
import net.minecraft.entity.player.EntityPlayer import net.minecraft.entity.player.EntityPlayer
import net.minecraft.nbt.NBTTagCompound import net.minecraft.nbt.NBTTagCompound
import net.minecraftforge.common.ForgeDirection
class TileEntityComputer(isClient: Boolean) extends TileEntityRotatable with IComputerEnvironment with ItemComponentProxy { class TileEntityComputer(isClient: Boolean) extends TileEntityRotatable with IComputerEnvironment with ItemComponentProxy with RedstoneEnabled {
def this() = this(false) def this() = this(false)
protected val computer = protected val computer =
@ -36,7 +38,7 @@ class TileEntityComputer(isClient: Boolean) extends TileEntityRotatable with ICo
computer.signal("component_removed", message.source.address); None computer.signal("component_removed", message.source.address); None
case Array(oldAddress: Integer) if message.name == "network.reconnect" && isRunning => case Array(oldAddress: Integer) if message.name == "network.reconnect" && isRunning =>
computer.signal("component_changed", message.source.address, oldAddress); None computer.signal("component_changed", message.source.address, oldAddress); None
case Array(name: String, args@_*) if message.name == "signal" => case Array(name: String, args@_*) if message.name == "computer.signal" =>
computer.signal(name, args: _*); None computer.signal(name, args: _*); None
case _ => None case _ => None
} }
@ -100,6 +102,15 @@ class TileEntityComputer(isClient: Boolean) extends TileEntityRotatable with ICo
ClientPacketSender.sendComputerStateRequest(this) ClientPacketSender.sendComputerStateRequest(this)
} }
// ----------------------------------------------------------------------- //
// RedstoneEnabled
// ----------------------------------------------------------------------- //
def input(side: ForgeDirection): Int = worldObj.isBlockProvidingPowerTo(
xCoord + side.offsetX, yCoord + side.offsetY, zCoord + side.offsetZ, side.getOpposite.ordinal)
// TODO output
// ----------------------------------------------------------------------- // // ----------------------------------------------------------------------- //
// Interfaces and updating // Interfaces and updating
// ----------------------------------------------------------------------- // // ----------------------------------------------------------------------- //

View File

@ -12,15 +12,15 @@ class TileEntityKeyboard extends TileEntityRotatable with INetworkNode {
super.receive(message) super.receive(message)
message.data match { message.data match {
case Array(p: Player, char: Char, code: Int) if message.name == "keyboard.keyDown" => if (isUseableByPlayer(p)) { case Array(p: Player, char: Char, code: Int) if message.name == "keyboard.keyDown" => if (isUseableByPlayer(p)) {
network.sendToAll(this, "signal", "key_down", char, code) network.sendToAll(this, "computer.signal", "key_down", char, code)
message.cancel() // One keyboard is enough. message.cancel() // One keyboard is enough.
} }
case Array(p: Player, char: Char, code: Int) if message.name == "keyboard.keyUp" => if (isUseableByPlayer(p)) { case Array(p: Player, char: Char, code: Int) if message.name == "keyboard.keyUp" => if (isUseableByPlayer(p)) {
network.sendToAll(this, "signal", "key_up", char, code) network.sendToAll(this, "computer.signal", "key_up", char, code)
message.cancel() // One keyboard is enough. message.cancel() // One keyboard is enough.
} }
case Array(p: Player, value: String) if message.name == "keyboard.clipboard" => if (isUseableByPlayer(p)) { case Array(p: Player, value: String) if message.name == "keyboard.clipboard" => if (isUseableByPlayer(p)) {
network.sendToAll(this, "signal", "clipboard", value) network.sendToAll(this, "computer.signal", "clipboard", value)
message.cancel() message.cancel()
} }
case _ => // Ignore. case _ => // Ignore.

View File

@ -32,7 +32,8 @@ class TileEntityScreen extends TileEntityRotatable with IScreenEnvironment {
// IScreenEnvironment // IScreenEnvironment
// ----------------------------------------------------------------------- // // ----------------------------------------------------------------------- //
def onScreenResolutionChange(w: Int, h: Int) = override def onScreenResolutionChange(w: Int, h: Int) = {
super.onScreenResolutionChange(w, h)
if (worldObj.isRemote) { if (worldObj.isRemote) {
gui.foreach(_.setSize(w, h)) gui.foreach(_.setSize(w, h))
} }
@ -40,8 +41,10 @@ class TileEntityScreen extends TileEntityRotatable with IScreenEnvironment {
markAsChanged() markAsChanged()
ServerPacketSender.sendScreenResolutionChange(this, w, h) ServerPacketSender.sendScreenResolutionChange(this, w, h)
} }
}
def onScreenSet(col: Int, row: Int, s: String) = override def onScreenSet(col: Int, row: Int, s: String) = {
super.onScreenSet(col, row, s)
if (worldObj.isRemote) { if (worldObj.isRemote) {
gui.foreach(_.updateText()) gui.foreach(_.updateText())
} }
@ -49,8 +52,10 @@ class TileEntityScreen extends TileEntityRotatable with IScreenEnvironment {
markAsChanged() markAsChanged()
ServerPacketSender.sendScreenSet(this, col, row, s) ServerPacketSender.sendScreenSet(this, col, row, s)
} }
}
def onScreenFill(col: Int, row: Int, w: Int, h: Int, c: Char) = override def onScreenFill(col: Int, row: Int, w: Int, h: Int, c: Char) = {
super.onScreenFill(col, row, w, h, c)
if (worldObj.isRemote) { if (worldObj.isRemote) {
gui.foreach(_.updateText()) gui.foreach(_.updateText())
} }
@ -58,8 +63,10 @@ class TileEntityScreen extends TileEntityRotatable with IScreenEnvironment {
markAsChanged() markAsChanged()
ServerPacketSender.sendScreenFill(this, col, row, w, h, c) ServerPacketSender.sendScreenFill(this, col, row, w, h, c)
} }
}
def onScreenCopy(col: Int, row: Int, w: Int, h: Int, tx: Int, ty: Int) = override def onScreenCopy(col: Int, row: Int, w: Int, h: Int, tx: Int, ty: Int) = {
super.onScreenCopy(col, row, w, h, tx, ty)
if (worldObj.isRemote) { if (worldObj.isRemote) {
gui.foreach(_.updateText()) gui.foreach(_.updateText())
} }
@ -67,6 +74,7 @@ class TileEntityScreen extends TileEntityRotatable with IScreenEnvironment {
markAsChanged() markAsChanged()
ServerPacketSender.sendScreenCopy(this, col, row, w, h, tx, ty) ServerPacketSender.sendScreenCopy(this, col, row, w, h, tx, ty)
} }
}
private def markAsChanged(): Unit = private def markAsChanged(): Unit =
worldObj.updateTileEntityChunkAndDoNothing( worldObj.updateTileEntityChunkAndDoNothing(

View File

@ -1,6 +1,7 @@
package li.cil.oc.common.util package li.cil.oc.common.util
import com.google.common.collect.MapMaker import com.google.common.collect.MapMaker
import li.cil.oc.server.components.ItemComponent
import net.minecraft.item.ItemStack import net.minecraft.item.ItemStack
import net.minecraft.nbt.NBTTagCompound import net.minecraft.nbt.NBTTagCompound
import scala.collection.{JavaConversions, mutable} import scala.collection.{JavaConversions, mutable}
@ -13,12 +14,12 @@ import scala.collection.{JavaConversions, mutable}
object ItemComponentCache { object ItemComponentCache {
private val caches = mutable.Map.empty[Int, Cache[_]] private val caches = mutable.Map.empty[Int, Cache[_]]
def get[T](item: ItemStack) = caches.get(item.itemID) match { def get[T <: ItemComponent](item: ItemStack) = caches.get(item.itemID) match {
case None => None case None => None
case Some(cache) => cache.asInstanceOf[Cache[T]].get(item) case Some(cache) => cache.asInstanceOf[Cache[T]].get(item)
} }
def register[T](id: Int, constructor: (NBTTagCompound) => T): Unit = def register[T <: ItemComponent](id: Int, constructor: (NBTTagCompound) => T): Unit =
caches += id -> new Cache[T](id, constructor) caches += id -> new Cache[T](id, constructor)
private class Cache[T](val id: Int, val constructor: (NBTTagCompound) => T) { private class Cache[T](val id: Int, val constructor: (NBTTagCompound) => T) {

View File

@ -1,5 +1,9 @@
package li.cil.oc.server.components package li.cil.oc.server.components
class Disk { import li.cil.oc.api.INetworkNode
class Disk extends INetworkNode{
override def name = "disk"
def close() {} def close() {}
} }

View File

@ -3,21 +3,13 @@ package li.cil.oc.server.components
import li.cil.oc.api.{INetworkNode, INetworkMessage} import li.cil.oc.api.{INetworkNode, INetworkMessage}
import net.minecraft.nbt.NBTTagCompound import net.minecraft.nbt.NBTTagCompound
class GraphicsCard(val nbt: NBTTagCompound) extends INetworkNode { class GraphicsCard(nbt: NBTTagCompound) extends ItemComponent(nbt) {
address = nbt.getInteger("address")
val supportedResolutions = List(List(40, 24), List(80, 24)) val supportedResolutions = List(List(40, 24), List(80, 24))
override def name = "gpu" override def name = "gpu"
override def address_=(value: Int) = {
super.address_=(value)
nbt.setInteger("address", address)
}
override def receive(message: INetworkMessage) = { override def receive(message: INetworkMessage) = {
// We don't need onConnect / onDisconnect / onAddressChange yet, so no need to call super. super.receive(message)
// super.receive(message)
message.data match { message.data match {
case Array(screen: Double, w: Double, h: Double) if message.name == "gpu.resolution=" => case Array(screen: Double, w: Double, h: Double) if message.name == "gpu.resolution=" =>
if (supportedResolutions.contains((w.toInt, h.toInt))) if (supportedResolutions.contains((w.toInt, h.toInt)))

View File

@ -0,0 +1,13 @@
package li.cil.oc.server.components
import li.cil.oc.api.INetworkNode
import net.minecraft.nbt.NBTTagCompound
abstract class ItemComponent(val nbt: NBTTagCompound) extends INetworkNode {
address = nbt.getInteger("address")
override def address_=(value: Int) = {
super.address_=(value)
nbt.setInteger("address", address)
}
}

View File

@ -0,0 +1,46 @@
package li.cil.oc.server.components
import li.cil.oc.api.INetworkMessage
import net.minecraft.nbt.NBTTagCompound
import net.minecraft.tileentity.TileEntity
import net.minecraftforge.common.ForgeDirection
class RedstoneCard(nbt: NBTTagCompound) extends ItemComponent(nbt) {
override def name = "redstone"
override def receive(message: INetworkMessage): Option[Array[Any]] = {
super.receive(message)
message.data match {
case Array(target: Double, side: Double) if message.name == "redstone.input" =>
input(target.toInt, side.toInt)
case Array(target: Double, side: Double) if message.name == "redstone.output" =>
output(target.toInt, side.toInt)
case Array(target: Double, side: Double, value: Double) if message.name == "redstone.output=" =>
output(target.toInt, side.toInt, value.toInt); None
case _ => None // Ignore.
}
}
private def input(target: Int, side: Int) = if (side >= 0 && side < 6) network.node(target) match {
case Some(r: RedstoneEnabled) => Some(Array(r.input(ForgeDirection.getOrientation(side)).asInstanceOf[Any]))
case Some(t: TileEntity) =>
val face = ForgeDirection.getOrientation(side.toInt)
val power = t.worldObj.isBlockProvidingPowerTo(
t.xCoord + face.offsetX, t.yCoord + face.offsetY, t.zCoord + face.offsetZ, face.getOpposite.ordinal)
Some(Array(power.asInstanceOf[Any]))
case _ => None // Can't work with this node.
} else None
private def output(target: Int, side: Int) = if (side >= 0 && side < 6) network.node(target) match {
case Some(r: RedstoneEnabled) => Some(Array(r.output(ForgeDirection.getOrientation(side)).asInstanceOf[Any]))
case Some(t: TileEntity) =>
val power = t.worldObj.isBlockProvidingPowerTo(t.xCoord, t.yCoord, t.zCoord, side.toInt)
Some(Array(power.asInstanceOf[Any]))
case _ => None // Can't work with this node.
} else None
private def output(target: Int, side: Int, value: Int) = if (side >= 0 && side < 6) network.node(target) match {
case Some(r: RedstoneEnabled) => r.output(ForgeDirection.getOrientation(side)) = value
case _ => // Can't work with this node.
}
}

View File

@ -0,0 +1,15 @@
package li.cil.oc.server.components
import net.minecraftforge.common.ForgeDirection
trait RedstoneEnabled {
protected val _output = Array.fill(6)(0)
def input(side: ForgeDirection): Int
def output = new {
def apply(side: ForgeDirection) = _output(side.ordinal)
def update(side: ForgeDirection, value: Int) = _output(side.ordinal) = value
}
}

View File

@ -43,7 +43,7 @@ class Computer(val owner: IComputerEnvironment) extends IComputer with Runnable
* resume the computers main thread, if at all, and whether to accept new * resume the computers main thread, if at all, and whether to accept new
* signals or not. * signals or not.
*/ */
private var state = State.Stopped private var state = Computer.State.Stopped
/** The internal Lua state. Only set while the computer is running. */ /** The internal Lua state. Only set while the computer is running. */
private[computer] var lua: LuaState = null private[computer] var lua: LuaState = null
@ -62,7 +62,7 @@ class Computer(val owner: IComputerEnvironment) extends IComputer with Runnable
* means to communicate actively with the computer (passively only message * means to communicate actively with the computer (passively only message
* handlers can interact with the computer by returning some result). * handlers can interact with the computer by returning some result).
*/ */
private val signals = new LinkedBlockingQueue[Signal](100) private val signals = new LinkedBlockingQueue[Computer.Signal](100)
// ----------------------------------------------------------------------- // // ----------------------------------------------------------------------- //
@ -121,23 +121,23 @@ class Computer(val owner: IComputerEnvironment) extends IComputer with Runnable
}.toArray }.toArray
stateMonitor.synchronized(state match { stateMonitor.synchronized(state match {
// We don't push new signals when stopped or shutting down. // We don't push new signals when stopped or shutting down.
case State.Stopped | State.Stopping => false case Computer.State.Stopped | Computer.State.Stopping => false
// Currently sleeping. Cancel that and start immediately. // Currently sleeping. Cancel that and start immediately.
case State.Sleeping => case Computer.State.Sleeping =>
val v = values // Map first, may error. val v = values // Map first, may error.
future.get.cancel(true) future.get.cancel(true)
state = State.Suspended state = Computer.State.Suspended
signals.offer(new Signal(name, v)) signals.offer(new Computer.Signal(name, v))
future = Some(Computer.Executor.pool.submit(this)) future = Some(Computer.Executor.pool.submit(this))
true true
// Basically running, but had nothing to do so we stopped. Resume. // Basically running, but had nothing to do so we stopped. Resume.
case State.Suspended if !future.isDefined => case Computer.State.Suspended if !future.isDefined =>
signals.offer(new Signal(name, values)) signals.offer(new Computer.Signal(name, values))
future = Some(Computer.Executor.pool.submit(this)) future = Some(Computer.Executor.pool.submit(this))
true true
// Running or in synchronized call, just push the signal. // Running or in synchronized call, just push the signal.
case _ => case _ =>
signals.offer(new Signal(name, values)) signals.offer(new Computer.Signal(name, values))
true true
}) })
} }
@ -147,8 +147,8 @@ class Computer(val owner: IComputerEnvironment) extends IComputer with Runnable
// ----------------------------------------------------------------------- // // ----------------------------------------------------------------------- //
override def start() = stateMonitor.synchronized( override def start() = stateMonitor.synchronized(
state == State.Stopped && init() && { state == Computer.State.Stopped && init() && {
state = State.Suspended state = Computer.State.Suspended
// Mark state change in owner, to send it to clients. // Mark state change in owner, to send it to clients.
owner.markAsChanged() owner.markAsChanged()
@ -165,8 +165,8 @@ class Computer(val owner: IComputerEnvironment) extends IComputer with Runnable
}) })
override def stop() = saveMonitor.synchronized(stateMonitor.synchronized { override def stop() = saveMonitor.synchronized(stateMonitor.synchronized {
if (state != State.Stopped) { if (state != Computer.State.Stopped) {
if (state != State.Running) { if (state != Computer.State.Running) {
// If the computer is not currently running we can simply close it, // If the computer is not currently running we can simply close it,
// and cancel any pending future - which may already be running and // and cancel any pending future - which may already be running and
// waiting for the stateMonitor, so we do a hard abort. // waiting for the stateMonitor, so we do a hard abort.
@ -178,26 +178,26 @@ class Computer(val owner: IComputerEnvironment) extends IComputer with Runnable
// truly stopped, before switching back to stopped to allow starting // truly stopped, before switching back to stopped to allow starting
// the computer again. The executor will check for this state and // the computer again. The executor will check for this state and
// call close. // call close.
state = State.Stopping state = Computer.State.Stopping
} }
true true
} }
else false else false
}) })
override def isRunning = stateMonitor.synchronized(state != State.Stopped) override def isRunning = stateMonitor.synchronized(state != Computer.State.Stopped)
override def update() { override def update() {
stateMonitor.synchronized(state match { stateMonitor.synchronized(state match {
case State.Stopped | State.Stopping => return case Computer.State.Stopped | Computer.State.Stopping => return
case State.SynchronizedCall => { case Computer.State.SynchronizedCall => {
assert(lua.getTop == 2) assert(lua.getTop == 2)
assert(lua.isThread(1)) assert(lua.isThread(1))
assert(lua.isFunction(2)) assert(lua.isFunction(2))
try { try {
lua.call(0, 1) lua.call(0, 1)
lua.checkType(2, LuaType.TABLE) lua.checkType(2, LuaType.TABLE)
state = State.SynchronizedReturn state = Computer.State.SynchronizedReturn
assert(!future.isDefined) assert(!future.isDefined)
future = Some(Computer.Executor.pool.submit(this)) future = Some(Computer.Executor.pool.submit(this))
} catch { } catch {
@ -212,26 +212,26 @@ class Computer(val owner: IComputerEnvironment) extends IComputer with Runnable
} }
} }
} }
case State.Paused => { case Computer.State.Paused => {
state = State.Suspended state = Computer.State.Suspended
assert(!future.isDefined) assert(!future.isDefined)
future = Some(Computer.Executor.pool.submit(this)) future = Some(Computer.Executor.pool.submit(this))
} }
case State.SynchronizedReturnPaused => { case Computer.State.SynchronizedReturnPaused => {
state = State.SynchronizedReturn state = Computer.State.SynchronizedReturn
assert(!future.isDefined) assert(!future.isDefined)
future = Some(Computer.Executor.pool.submit(this)) future = Some(Computer.Executor.pool.submit(this))
} }
case _ => /* Nothing special to do. */ case _ => /* Nothing special to do. */
}) })
// Update world time for computer threads.
worldTime = owner.world.getWorldInfo.getWorldTotalTime
// Remember when we started the computer for os.clock(). We do this in the // Remember when we started the computer for os.clock(). We do this in the
// update because only then can we be sure the world is available. // update because only then can we be sure the world is available.
if (timeStarted == 0) if (timeStarted == 0)
timeStarted = owner.world.getWorldInfo.getWorldTotalTime timeStarted = worldTime
// Update world time for computer threads.
worldTime = owner.world.getWorldInfo.getWorldTotalTime
// Update last time run to let our executor thread know it doesn't have to // Update last time run to let our executor thread know it doesn't have to
// pause. // pause.
@ -244,13 +244,13 @@ class Computer(val owner: IComputerEnvironment) extends IComputer with Runnable
saveMonitor.synchronized(this.synchronized { saveMonitor.synchronized(this.synchronized {
// Clear out what we currently have, if anything. // Clear out what we currently have, if anything.
stateMonitor.synchronized { stateMonitor.synchronized {
assert(state != State.Running) // Lock on 'this' should guarantee this. assert(state != Computer.State.Running) // Lock on 'this' should guarantee this.
stop() stop()
} }
state = State(nbt.getInteger("state")) state = Computer.State(nbt.getInteger("state"))
if (state != State.Stopped && init()) { if (state != Computer.State.Stopped && init()) {
// Unlimit memory use while unpersisting. // Unlimit memory use while unpersisting.
val memory = lua.getTotalMemory val memory = lua.getTotalMemory
lua.setTotalMemory(Integer.MAX_VALUE) lua.setTotalMemory(Integer.MAX_VALUE)
@ -264,10 +264,10 @@ class Computer(val owner: IComputerEnvironment) extends IComputer with Runnable
// the save was corrupt (maybe someone modified the Lua files). // the save was corrupt (maybe someone modified the Lua files).
throw new IllegalStateException("Could not restore kernel.") throw new IllegalStateException("Could not restore kernel.")
} }
if (state == State.SynchronizedCall || state == State.SynchronizedReturn) { if (state == Computer.State.SynchronizedCall || state == Computer.State.SynchronizedReturn) {
if (!unpersist(nbt.getByteArray("stack")) || if (!unpersist(nbt.getByteArray("stack")) ||
(state == State.SynchronizedCall && !lua.isFunction(2)) || (state == Computer.State.SynchronizedCall && !lua.isFunction(2)) ||
(state == State.SynchronizedReturn && !lua.isTable(2))) { (state == Computer.State.SynchronizedReturn && !lua.isTable(2))) {
// Same as with the above, should not really happen normally, but // Same as with the above, should not really happen normally, but
// could for the same reasons. // could for the same reasons.
throw new IllegalStateException("Could not restore stack.") throw new IllegalStateException("Could not restore stack.")
@ -282,7 +282,7 @@ class Computer(val owner: IComputerEnvironment) extends IComputer with Runnable
map(signal => { map(signal => {
val argsTag = signal.getCompoundTag("args") val argsTag = signal.getCompoundTag("args")
val argsLength = argsTag.getInteger("length") val argsLength = argsTag.getInteger("length")
new Signal(signal.getString("name"), new Computer.Signal(signal.getString("name"),
(0 until argsLength).map("arg" + _).map(argsTag.getTag).map { (0 until argsLength).map("arg" + _).map(argsTag.getTag).map {
case tag: NBTTagByte if tag.data == -1 => Unit case tag: NBTTagByte if tag.data == -1 => Unit
case tag: NBTTagByte => tag.data == 1 case tag: NBTTagByte => tag.data == 1
@ -300,7 +300,7 @@ class Computer(val owner: IComputerEnvironment) extends IComputer with Runnable
// Start running our worker thread. // Start running our worker thread.
assert(!future.isDefined) assert(!future.isDefined)
state match { state match {
case State.Suspended | State.Sleeping | State.SynchronizedReturn => case Computer.State.Suspended | Computer.State.Sleeping | Computer.State.SynchronizedReturn =>
future = Some(Computer.Executor.pool.submit(this)) future = Some(Computer.Executor.pool.submit(this))
case _ => // Wasn't running before. case _ => // Wasn't running before.
} }
@ -320,12 +320,12 @@ class Computer(val owner: IComputerEnvironment) extends IComputer with Runnable
override def save(nbt: NBTTagCompound): Unit = override def save(nbt: NBTTagCompound): Unit =
saveMonitor.synchronized(this.synchronized { saveMonitor.synchronized(this.synchronized {
stateMonitor.synchronized { stateMonitor.synchronized {
assert(state != State.Running) // Lock on 'this' should guarantee this. assert(state != Computer.State.Running) // Lock on 'this' should guarantee this.
assert(state != State.Stopping) // Only set while executor is running. assert(state != Computer.State.Stopping) // Only set while executor is running.
} }
nbt.setInteger("state", state.id) nbt.setInteger("state", state.id)
if (state == State.Stopped) { if (state == Computer.State.Stopped) {
return return
} }
@ -336,8 +336,8 @@ class Computer(val owner: IComputerEnvironment) extends IComputer with Runnable
// Try persisting Lua, because that's what all of the rest depends on. // Try persisting Lua, because that's what all of the rest depends on.
// While in a driver call we have one object on the global stack: either // While in a driver call we have one object on the global stack: either
// the function to call the driver with, or the result of the call. // the function to call the driver with, or the result of the call.
if (state == State.SynchronizedCall || state == State.SynchronizedReturn) { if (state == Computer.State.SynchronizedCall || state == Computer.State.SynchronizedReturn) {
assert(if (state == State.SynchronizedCall) lua.isFunction(2) else lua.isTable(2)) assert(if (state == Computer.State.SynchronizedCall) lua.isFunction(2) else lua.isTable(2))
nbt.setByteArray("stack", persist(2)) nbt.setByteArray("stack", persist(2))
} }
// Save the kernel state (which is always at stack index one). // Save the kernel state (which is always at stack index one).
@ -366,7 +366,7 @@ class Computer(val owner: IComputerEnvironment) extends IComputer with Runnable
catch { catch {
case t: Throwable => { case t: Throwable => {
t.printStackTrace() t.printStackTrace()
nbt.setInteger("state", State.Stopped.id) nbt.setInteger("state", Computer.State.Stopped.id)
} }
} }
finally { finally {
@ -456,6 +456,13 @@ class Computer(val owner: IComputerEnvironment) extends IComputer with Runnable
})) }))
lua.setField(-2, "romSize") lua.setField(-2, "romSize")
// Allow the computer to figure out its own id in the component network.
lua.pushJavaFunction(ScalaFunction(lua => {
lua.pushInteger(owner.address)
1
}))
lua.setField(-2, "address")
// Pop the os table. // Pop the os table.
lua.pop(1) lua.pop(1)
@ -596,8 +603,8 @@ class Computer(val owner: IComputerEnvironment) extends IComputer with Runnable
} }
private def close(): Unit = stateMonitor.synchronized( private def close(): Unit = stateMonitor.synchronized(
if (state != State.Stopped) { if (state != Computer.State.Stopped) {
state = State.Stopped state = Computer.State.Stopped
lua.setTotalMemory(Integer.MAX_VALUE) lua.setTotalMemory(Integer.MAX_VALUE)
lua.close() lua.close()
lua = null lua = null
@ -616,21 +623,21 @@ class Computer(val owner: IComputerEnvironment) extends IComputer with Runnable
if (System.currentTimeMillis - lastUpdate > 200) if (System.currentTimeMillis - lastUpdate > 200)
stateMonitor.synchronized { stateMonitor.synchronized {
state = state =
if (state == State.SynchronizedReturn) State.SynchronizedReturnPaused if (state == Computer.State.SynchronizedReturn) Computer.State.SynchronizedReturnPaused
else State.Paused else Computer.State.Paused
future = None future = None
return return
} }
val callReturn = stateMonitor.synchronized { val callReturn = stateMonitor.synchronized {
if (state == State.Stopped) return if (state == Computer.State.Stopped) return
val oldState = state val oldState = state
state = State.Running state = Computer.State.Running
future = None future = None
oldState oldState
} match { } match {
case State.SynchronizedReturn | State.SynchronizedReturnPaused => true case Computer.State.SynchronizedReturn | Computer.State.SynchronizedReturnPaused => true
case State.Stopped | State.Paused | State.Suspended | State.Sleeping => false case Computer.State.Stopped | Computer.State.Paused | Computer.State.Suspended | Computer.State.Sleeping => false
case s => case s =>
OpenComputers.log.warning("Running computer from invalid state " + s.toString + "!") OpenComputers.log.warning("Running computer from invalid state " + s.toString + "!")
stateMonitor.synchronized { stateMonitor.synchronized {
@ -678,7 +685,7 @@ class Computer(val owner: IComputerEnvironment) extends IComputer with Runnable
if (lua.status(1) == LuaState.YIELD) { if (lua.status(1) == LuaState.YIELD) {
// Lua state yielded normally, see what we have. // Lua state yielded normally, see what we have.
stateMonitor.synchronized { stateMonitor.synchronized {
if (state == State.Stopping) { if (state == Computer.State.Stopping) {
// Someone called stop() in the meantime. // Someone called stop() in the meantime.
close() close()
} }
@ -688,25 +695,25 @@ class Computer(val owner: IComputerEnvironment) extends IComputer with Runnable
val sleep = (lua.toNumber(2) * 1000).toLong val sleep = (lua.toNumber(2) * 1000).toLong
lua.pop(results) lua.pop(results)
if (signals.isEmpty) { if (signals.isEmpty) {
state = State.Sleeping state = Computer.State.Sleeping
assert(!future.isDefined) assert(!future.isDefined)
future = Some(Computer.Executor.pool.schedule(this, sleep, TimeUnit.MILLISECONDS)) future = Some(Computer.Executor.pool.schedule(this, sleep, TimeUnit.MILLISECONDS))
} }
else { else {
state = State.Suspended state = Computer.State.Suspended
assert(!future.isDefined) assert(!future.isDefined)
future = Some(Computer.Executor.pool.submit(this)) future = Some(Computer.Executor.pool.submit(this))
} }
} }
else if (results == 1 && lua.isFunction(2)) { else if (results == 1 && lua.isFunction(2)) {
// If we get one function it's a wrapper for a synchronized call. // If we get one function it's a wrapper for a synchronized call.
state = State.SynchronizedCall state = Computer.State.SynchronizedCall
assert(!future.isDefined) assert(!future.isDefined)
} }
else { else {
// Something else, just pop the results and try again. // Something else, just pop the results and try again.
lua.pop(results) lua.pop(results)
state = State.Suspended state = Computer.State.Suspended
assert(!future.isDefined) assert(!future.isDefined)
if (!signals.isEmpty) future = Some(Computer.Executor.pool.submit(this)) if (!signals.isEmpty) future = Some(Computer.Executor.pool.submit(this))
} }
@ -736,6 +743,19 @@ class Computer(val owner: IComputerEnvironment) extends IComputer with Runnable
// If we come here there was an error or we stopped, kill off the state. // If we come here there was an error or we stopped, kill off the state.
close() close()
} }
}
object Computer {
@ForgeSubscribe
def onChunkUnload(e: ChunkEvent.Unload) =
onUnload(e.world, e.getChunk.chunkTileEntityMap.values.map(_.asInstanceOf[TileEntity]))
private def onUnload(w: World, tileEntities: Iterable[TileEntity]) = if (!w.isRemote) {
tileEntities.
filter(_.isInstanceOf[TileEntityComputer]).
map(_.asInstanceOf[TileEntityComputer]).
foreach(_.turnOff())
}
/** Signals are messages sent to the Lua state from Java asynchronously. */ /** Signals are messages sent to the Lua state from Java asynchronously. */
private class Signal(val name: String, val args: Array[Any]) private class Signal(val name: String, val args: Array[Any])
@ -770,20 +790,6 @@ class Computer(val owner: IComputerEnvironment) extends IComputer with Runnable
val SynchronizedReturnPaused = Value("SynchronizedReturnPaused") val SynchronizedReturnPaused = Value("SynchronizedReturnPaused")
} }
}
object Computer {
@ForgeSubscribe
def onChunkUnload(e: ChunkEvent.Unload) =
onUnload(e.world, e.getChunk.chunkTileEntityMap.values.map(_.asInstanceOf[TileEntity]))
private def onUnload(w: World, tileEntities: Iterable[TileEntity]) = if (!w.isRemote) {
tileEntities.
filter(_.isInstanceOf[TileEntityComputer]).
map(_.asInstanceOf[TileEntityComputer]).
foreach(_.turnOff())
}
/** Singleton for requesting executors that run our Lua states. */ /** Singleton for requesting executors that run our Lua states. */
private object Executor { private object Executor {
val pool = Executors.newScheduledThreadPool(Config.threads, val pool = Executors.newScheduledThreadPool(Config.threads,

View File

@ -0,0 +1,17 @@
package li.cil.oc.server.drivers
import li.cil.oc.api.{ComponentType, IItemDriver}
import li.cil.oc.common.util.ItemComponentCache
import li.cil.oc.server.components.RedstoneCard
import net.minecraft.item.ItemStack
import li.cil.oc.Items
object RedstoneDriver extends IItemDriver {
override def api = Option(getClass.getResourceAsStream("/assets/opencomputers/lua/redstone.lua"))
override def worksWith(item: ItemStack) = item.itemID == Items.rs.itemID
override def componentType(item: ItemStack) = ComponentType.PCI
override def node(item: ItemStack) = ItemComponentCache.get[RedstoneCard](item)
}