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

View File

@ -137,5 +137,5 @@ for k, v in pairs(driver.keyboard.keys) do
end
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

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
import li.cil.oc.common.items.ItemGraphicsCard
import li.cil.oc.common.items.ItemHDD
import li.cil.oc.common.items.{ItemRedstoneCard, ItemGraphicsCard, ItemHDD}
import li.cil.oc.common.util.ItemComponentCache
import li.cil.oc.server.components.GraphicsCard
import li.cil.oc.server.components.{RedstoneCard, GraphicsCard}
object Items {
var gpu: ItemGraphicsCard = null
var hdd: ItemHDD = null
var rs: ItemRedstoneCard = null
def init() {
gpu = new ItemGraphicsCard
hdd = new ItemHDD
rs = new ItemRedstoneCard
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/>
* Messages should have a unique name to allow differentiating them when
* 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
* inject that signal into the Lua VM, so no message not used for this purpose
* should be named "signal".
* messages named "computer.signal" by converting the message data to a
* signal and inject that signal into the Lua VM, so no message not used for
* this purpose should be named "computer.signal".
* <p/>
* 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
@ -144,9 +144,9 @@ trait INetwork {
* <p/>
* Messages should have a unique name to allow differentiating them when
* 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
* inject that signal into the Lua VM, so no message not used for this purpose
* should be named "signal".
* messages named "computer.signal" by converting the message data to a
* signal and inject that signal into the Lua VM, so no message not used for
* this purpose should be named "computer.signal".
*
* @param source the node that sends the message.
* @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) {
setMaxStackSize(1)
//setHasSubtypes(true)
setUnlocalizedName("oc.gpu")
setUnlocalizedName("Graphics Card")
setCreativeTab(CreativeTab)
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.client.computer.{Computer => ClientComputer}
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.{Computer => ServerComputer}
import li.cil.oc.server.{PacketSender => ServerPacketSender}
import net.minecraft.entity.player.EntityPlayer
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)
protected val computer =
@ -36,7 +38,7 @@ class TileEntityComputer(isClient: Boolean) extends TileEntityRotatable with ICo
computer.signal("component_removed", message.source.address); None
case Array(oldAddress: Integer) if message.name == "network.reconnect" && isRunning =>
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
case _ => None
}
@ -100,6 +102,15 @@ class TileEntityComputer(isClient: Boolean) extends TileEntityRotatable with ICo
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
// ----------------------------------------------------------------------- //

View File

@ -12,15 +12,15 @@ class TileEntityKeyboard extends TileEntityRotatable with INetworkNode {
super.receive(message)
message.data match {
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.
}
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.
}
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()
}
case _ => // Ignore.

View File

@ -32,7 +32,8 @@ class TileEntityScreen extends TileEntityRotatable with IScreenEnvironment {
// IScreenEnvironment
// ----------------------------------------------------------------------- //
def onScreenResolutionChange(w: Int, h: Int) =
override def onScreenResolutionChange(w: Int, h: Int) = {
super.onScreenResolutionChange(w, h)
if (worldObj.isRemote) {
gui.foreach(_.setSize(w, h))
}
@ -40,8 +41,10 @@ class TileEntityScreen extends TileEntityRotatable with IScreenEnvironment {
markAsChanged()
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) {
gui.foreach(_.updateText())
}
@ -49,8 +52,10 @@ class TileEntityScreen extends TileEntityRotatable with IScreenEnvironment {
markAsChanged()
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) {
gui.foreach(_.updateText())
}
@ -58,8 +63,10 @@ class TileEntityScreen extends TileEntityRotatable with IScreenEnvironment {
markAsChanged()
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) {
gui.foreach(_.updateText())
}
@ -67,6 +74,7 @@ class TileEntityScreen extends TileEntityRotatable with IScreenEnvironment {
markAsChanged()
ServerPacketSender.sendScreenCopy(this, col, row, w, h, tx, ty)
}
}
private def markAsChanged(): Unit =
worldObj.updateTileEntityChunkAndDoNothing(

View File

@ -1,6 +1,7 @@
package li.cil.oc.common.util
import com.google.common.collect.MapMaker
import li.cil.oc.server.components.ItemComponent
import net.minecraft.item.ItemStack
import net.minecraft.nbt.NBTTagCompound
import scala.collection.{JavaConversions, mutable}
@ -13,12 +14,12 @@ import scala.collection.{JavaConversions, mutable}
object ItemComponentCache {
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 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)
private class Cache[T](val id: Int, val constructor: (NBTTagCompound) => T) {

View File

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

View File

@ -3,21 +3,13 @@ package li.cil.oc.server.components
import li.cil.oc.api.{INetworkNode, INetworkMessage}
import net.minecraft.nbt.NBTTagCompound
class GraphicsCard(val nbt: NBTTagCompound) extends INetworkNode {
address = nbt.getInteger("address")
class GraphicsCard(nbt: NBTTagCompound) extends ItemComponent(nbt) {
val supportedResolutions = List(List(40, 24), List(80, 24))
override def name = "gpu"
override def address_=(value: Int) = {
super.address_=(value)
nbt.setInteger("address", address)
}
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 {
case Array(screen: Double, w: Double, h: Double) if message.name == "gpu.resolution=" =>
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
* 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. */
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
* 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
stateMonitor.synchronized(state match {
// 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.
case State.Sleeping =>
case Computer.State.Sleeping =>
val v = values // Map first, may error.
future.get.cancel(true)
state = State.Suspended
signals.offer(new Signal(name, v))
state = Computer.State.Suspended
signals.offer(new Computer.Signal(name, v))
future = Some(Computer.Executor.pool.submit(this))
true
// Basically running, but had nothing to do so we stopped. Resume.
case State.Suspended if !future.isDefined =>
signals.offer(new Signal(name, values))
case Computer.State.Suspended if !future.isDefined =>
signals.offer(new Computer.Signal(name, values))
future = Some(Computer.Executor.pool.submit(this))
true
// Running or in synchronized call, just push the signal.
case _ =>
signals.offer(new Signal(name, values))
signals.offer(new Computer.Signal(name, values))
true
})
}
@ -147,8 +147,8 @@ class Computer(val owner: IComputerEnvironment) extends IComputer with Runnable
// ----------------------------------------------------------------------- //
override def start() = stateMonitor.synchronized(
state == State.Stopped && init() && {
state = State.Suspended
state == Computer.State.Stopped && init() && {
state = Computer.State.Suspended
// Mark state change in owner, to send it to clients.
owner.markAsChanged()
@ -165,8 +165,8 @@ class Computer(val owner: IComputerEnvironment) extends IComputer with Runnable
})
override def stop() = saveMonitor.synchronized(stateMonitor.synchronized {
if (state != State.Stopped) {
if (state != State.Running) {
if (state != Computer.State.Stopped) {
if (state != Computer.State.Running) {
// If the computer is not currently running we can simply close it,
// and cancel any pending future - which may already be running and
// 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
// the computer again. The executor will check for this state and
// call close.
state = State.Stopping
state = Computer.State.Stopping
}
true
}
else false
})
override def isRunning = stateMonitor.synchronized(state != State.Stopped)
override def isRunning = stateMonitor.synchronized(state != Computer.State.Stopped)
override def update() {
stateMonitor.synchronized(state match {
case State.Stopped | State.Stopping => return
case State.SynchronizedCall => {
case Computer.State.Stopped | Computer.State.Stopping => return
case Computer.State.SynchronizedCall => {
assert(lua.getTop == 2)
assert(lua.isThread(1))
assert(lua.isFunction(2))
try {
lua.call(0, 1)
lua.checkType(2, LuaType.TABLE)
state = State.SynchronizedReturn
state = Computer.State.SynchronizedReturn
assert(!future.isDefined)
future = Some(Computer.Executor.pool.submit(this))
} catch {
@ -212,26 +212,26 @@ class Computer(val owner: IComputerEnvironment) extends IComputer with Runnable
}
}
}
case State.Paused => {
state = State.Suspended
case Computer.State.Paused => {
state = Computer.State.Suspended
assert(!future.isDefined)
future = Some(Computer.Executor.pool.submit(this))
}
case State.SynchronizedReturnPaused => {
state = State.SynchronizedReturn
case Computer.State.SynchronizedReturnPaused => {
state = Computer.State.SynchronizedReturn
assert(!future.isDefined)
future = Some(Computer.Executor.pool.submit(this))
}
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
// update because only then can we be sure the world is available.
if (timeStarted == 0)
timeStarted = owner.world.getWorldInfo.getWorldTotalTime
// Update world time for computer threads.
worldTime = owner.world.getWorldInfo.getWorldTotalTime
timeStarted = worldTime
// Update last time run to let our executor thread know it doesn't have to
// pause.
@ -244,13 +244,13 @@ class Computer(val owner: IComputerEnvironment) extends IComputer with Runnable
saveMonitor.synchronized(this.synchronized {
// Clear out what we currently have, if anything.
stateMonitor.synchronized {
assert(state != State.Running) // Lock on 'this' should guarantee this.
assert(state != Computer.State.Running) // Lock on 'this' should guarantee this.
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.
val memory = lua.getTotalMemory
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).
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")) ||
(state == State.SynchronizedCall && !lua.isFunction(2)) ||
(state == State.SynchronizedReturn && !lua.isTable(2))) {
(state == Computer.State.SynchronizedCall && !lua.isFunction(2)) ||
(state == Computer.State.SynchronizedReturn && !lua.isTable(2))) {
// Same as with the above, should not really happen normally, but
// could for the same reasons.
throw new IllegalStateException("Could not restore stack.")
@ -282,7 +282,7 @@ class Computer(val owner: IComputerEnvironment) extends IComputer with Runnable
map(signal => {
val argsTag = signal.getCompoundTag("args")
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 {
case tag: NBTTagByte if tag.data == -1 => Unit
case tag: NBTTagByte => tag.data == 1
@ -300,7 +300,7 @@ class Computer(val owner: IComputerEnvironment) extends IComputer with Runnable
// Start running our worker thread.
assert(!future.isDefined)
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))
case _ => // Wasn't running before.
}
@ -320,12 +320,12 @@ class Computer(val owner: IComputerEnvironment) extends IComputer with Runnable
override def save(nbt: NBTTagCompound): Unit =
saveMonitor.synchronized(this.synchronized {
stateMonitor.synchronized {
assert(state != State.Running) // Lock on 'this' should guarantee this.
assert(state != State.Stopping) // Only set while executor is running.
assert(state != Computer.State.Running) // Lock on 'this' should guarantee this.
assert(state != Computer.State.Stopping) // Only set while executor is running.
}
nbt.setInteger("state", state.id)
if (state == State.Stopped) {
if (state == Computer.State.Stopped) {
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.
// 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.
if (state == State.SynchronizedCall || state == State.SynchronizedReturn) {
assert(if (state == State.SynchronizedCall) lua.isFunction(2) else lua.isTable(2))
if (state == Computer.State.SynchronizedCall || state == Computer.State.SynchronizedReturn) {
assert(if (state == Computer.State.SynchronizedCall) lua.isFunction(2) else lua.isTable(2))
nbt.setByteArray("stack", persist(2))
}
// 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 {
case t: Throwable => {
t.printStackTrace()
nbt.setInteger("state", State.Stopped.id)
nbt.setInteger("state", Computer.State.Stopped.id)
}
}
finally {
@ -456,6 +456,13 @@ class Computer(val owner: IComputerEnvironment) extends IComputer with Runnable
}))
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.
lua.pop(1)
@ -596,8 +603,8 @@ class Computer(val owner: IComputerEnvironment) extends IComputer with Runnable
}
private def close(): Unit = stateMonitor.synchronized(
if (state != State.Stopped) {
state = State.Stopped
if (state != Computer.State.Stopped) {
state = Computer.State.Stopped
lua.setTotalMemory(Integer.MAX_VALUE)
lua.close()
lua = null
@ -616,21 +623,21 @@ class Computer(val owner: IComputerEnvironment) extends IComputer with Runnable
if (System.currentTimeMillis - lastUpdate > 200)
stateMonitor.synchronized {
state =
if (state == State.SynchronizedReturn) State.SynchronizedReturnPaused
else State.Paused
if (state == Computer.State.SynchronizedReturn) Computer.State.SynchronizedReturnPaused
else Computer.State.Paused
future = None
return
}
val callReturn = stateMonitor.synchronized {
if (state == State.Stopped) return
if (state == Computer.State.Stopped) return
val oldState = state
state = State.Running
state = Computer.State.Running
future = None
oldState
} match {
case State.SynchronizedReturn | State.SynchronizedReturnPaused => true
case State.Stopped | State.Paused | State.Suspended | State.Sleeping => false
case Computer.State.SynchronizedReturn | Computer.State.SynchronizedReturnPaused => true
case Computer.State.Stopped | Computer.State.Paused | Computer.State.Suspended | Computer.State.Sleeping => false
case s =>
OpenComputers.log.warning("Running computer from invalid state " + s.toString + "!")
stateMonitor.synchronized {
@ -678,7 +685,7 @@ class Computer(val owner: IComputerEnvironment) extends IComputer with Runnable
if (lua.status(1) == LuaState.YIELD) {
// Lua state yielded normally, see what we have.
stateMonitor.synchronized {
if (state == State.Stopping) {
if (state == Computer.State.Stopping) {
// Someone called stop() in the meantime.
close()
}
@ -688,25 +695,25 @@ class Computer(val owner: IComputerEnvironment) extends IComputer with Runnable
val sleep = (lua.toNumber(2) * 1000).toLong
lua.pop(results)
if (signals.isEmpty) {
state = State.Sleeping
state = Computer.State.Sleeping
assert(!future.isDefined)
future = Some(Computer.Executor.pool.schedule(this, sleep, TimeUnit.MILLISECONDS))
}
else {
state = State.Suspended
state = Computer.State.Suspended
assert(!future.isDefined)
future = Some(Computer.Executor.pool.submit(this))
}
}
else if (results == 1 && lua.isFunction(2)) {
// If we get one function it's a wrapper for a synchronized call.
state = State.SynchronizedCall
state = Computer.State.SynchronizedCall
assert(!future.isDefined)
}
else {
// Something else, just pop the results and try again.
lua.pop(results)
state = State.Suspended
state = Computer.State.Suspended
assert(!future.isDefined)
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.
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. */
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")
}
}
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. */
private object Executor {
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)
}