mirror of
https://github.com/MightyPirates/OpenComputers.git
synced 2025-09-09 15:25:56 -04:00
os.reboot() and os.shutdown(); term.keyboardId to control which keyboard's input to listen to; added network "visibility" flag for nodes, controls which other nodes receive connect/reconnect messages from them; some more validation of network node validation (i.e. not sending to / receiving from nodes with address < 1 or visibility == none); fixed margin rendering for screen gui; always injecting the address of a sender for a signal received via the network; fixed item localization; made computers start/stop/queryable via network; major overhaul of multi-threading in computer class, not relying on future.cancel anymore (which apparently didn't interrupt running threads even though it should have) and centralized the state switching a bit (no longer any in signal() for example). removed the saveMonitor (if someone calls us from something other than the server thread its their fault).
This commit is contained in:
parent
90f4b2d96f
commit
d24f726632
@ -1,3 +1,4 @@
|
||||
oc.block.Computer.name=Computer
|
||||
oc.block.Screen.name=Screen
|
||||
oc.container.computer=Computer
|
||||
oc.container.computer=Computer
|
||||
oc.item.GraphicsCard.name=Graphics Card
|
@ -54,7 +54,10 @@ end
|
||||
|
||||
--[[ Dispatch an event with the specified parameter. ]]
|
||||
function event.fire(name, ...)
|
||||
-- We may have no arguments at all if the call is just used to drive the
|
||||
-- timer check (for example if we had no signal in coroutine.sleep()).
|
||||
if name then
|
||||
checkArg(1, name, "string")
|
||||
for callback, _ in pairs(listenersFor(name, false)) do
|
||||
local result, message = xpcall(callback, event.error, name, ...)
|
||||
if not result and message then
|
||||
@ -68,15 +71,18 @@ function event.fire(name, ...)
|
||||
end
|
||||
end
|
||||
end
|
||||
-- Collect elapsed callbacks first, since calling them may in turn lead to
|
||||
-- new timers being registered, which would add entries to the table we're
|
||||
-- iterating, which is not supported.
|
||||
local elapsed = {}
|
||||
for id, info in pairs(timers) do
|
||||
if info.after < os.clock() then
|
||||
table.insert(elapsed, info)
|
||||
table.insert(elapsed, info.callback)
|
||||
timers[id] = nil
|
||||
end
|
||||
end
|
||||
for _, info in ipairs(elapsed) do
|
||||
local result, message = xpcall(info.callback, event.error)
|
||||
for _, callback in ipairs(elapsed) do
|
||||
local result, message = xpcall(callback, event.error)
|
||||
if not result and message then
|
||||
error(message, 0)
|
||||
end
|
||||
@ -160,34 +166,46 @@ end)
|
||||
|
||||
event.listen("component_removed", function(_, address)
|
||||
local id = component.id(address)
|
||||
components[id] = nil
|
||||
event.fire("component_uninstalled", id)
|
||||
if id then
|
||||
components[id] = nil
|
||||
event.fire("component_uninstalled", id)
|
||||
end
|
||||
end)
|
||||
|
||||
event.listen("component_changed", function(_, newAddress, oldAddress)
|
||||
local id = component.id(oldAddress)
|
||||
components[id].address = newAddress
|
||||
if oldAddress > 0 and not id then return end
|
||||
if oldAddress > 0 and newAddress == 0 then -- ~0 -> 0
|
||||
components[id] = nil
|
||||
event.fire("component_uninstalled", id)
|
||||
elseif oldAddress == 0 and newAddress > 0 then -- 0 -> ~0
|
||||
id = #components + 1
|
||||
components[id] = {address = newAddress, name = driver.componentType(newAddress)}
|
||||
event.fire("component_installed", id)
|
||||
elseif oldAddress > 0 and newAddress > 0 then -- ~0 -> ~0
|
||||
components[id].address = newAddress
|
||||
end
|
||||
end)
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
--[[ Setup terminal API. ]]
|
||||
local idGpu, idScreen = 0, 0
|
||||
local gpuId, screenId = 0, 0
|
||||
local screenWidth, screenHeight = 0, 0
|
||||
local boundGpu = nil
|
||||
local cursorX, cursorY = 1, 1
|
||||
|
||||
event.listen("component_installed", function(_, id)
|
||||
local type = component.type(id)
|
||||
if type == "gpu" and idGpu < 1 then
|
||||
if type == "gpu" and gpuId < 1 then
|
||||
term.gpuId(id)
|
||||
elseif type == "screen" and idScreen < 1 then
|
||||
elseif type == "screen" and screenId < 1 then
|
||||
term.screenId(id)
|
||||
end
|
||||
end)
|
||||
|
||||
event.listen("component_uninstalled", function(_, id)
|
||||
if idGpu == id then
|
||||
if gpuId == id then
|
||||
term.gpuId(0)
|
||||
for id in component.ids() do
|
||||
if component.type(id) == "gpu" then
|
||||
@ -195,7 +213,7 @@ event.listen("component_uninstalled", function(_, id)
|
||||
return
|
||||
end
|
||||
end
|
||||
elseif idScreen == id then
|
||||
elseif screenId == id then
|
||||
term.screenId(0)
|
||||
for id in component.ids() do
|
||||
if component.type(id) == "screen" then
|
||||
@ -208,17 +226,17 @@ end)
|
||||
|
||||
event.listen("screen_resized", function(_, address, w, h)
|
||||
local id = component.id(address)
|
||||
if id == idScreen then
|
||||
if id == screenId then
|
||||
screenWidth = w
|
||||
screenHeight = h
|
||||
end
|
||||
end)
|
||||
|
||||
local function bindIfPossible()
|
||||
if idGpu > 0 and idScreen > 0 then
|
||||
if gpuId > 0 and screenId > 0 then
|
||||
if not boundGpu then
|
||||
local function gpu() return component.address(idGpu) end
|
||||
local function screen() return component.address(idScreen) end
|
||||
local function gpu() return component.address(gpuId) end
|
||||
local function screen() return component.address(screenId) end
|
||||
boundGpu = driver.gpu.bind(gpu, screen)
|
||||
screenWidth, screenHeight = boundGpu.getResolution()
|
||||
event.fire("term_available")
|
||||
@ -243,19 +261,19 @@ end
|
||||
function term.gpuId(id)
|
||||
if id then
|
||||
checkArg(1, id, "number")
|
||||
idGpu = id
|
||||
gpuId = id
|
||||
bindIfPossible()
|
||||
end
|
||||
return idGpu
|
||||
return gpuId
|
||||
end
|
||||
|
||||
function term.screenId(id)
|
||||
if id then
|
||||
checkArg(1, id, "number")
|
||||
idScreen = id
|
||||
screenId = id
|
||||
bindIfPossible()
|
||||
end
|
||||
return idScreen
|
||||
return screenId
|
||||
end
|
||||
|
||||
function term.getCursor()
|
||||
@ -312,10 +330,21 @@ function term.clear()
|
||||
cursorX, cursorY = 1, 1
|
||||
end
|
||||
|
||||
function term.clearLine()
|
||||
if not boundGpu then return end
|
||||
boundGpu.fill(1, cursorY, screenWidth, 1, " ")
|
||||
cursorX = 1
|
||||
end
|
||||
|
||||
-- Set custom write function to replace the dummy.
|
||||
write = function(...)
|
||||
local args = {...}
|
||||
local first = true
|
||||
for _, value in ipairs(args) do
|
||||
if not first then
|
||||
term.write(", ")
|
||||
end
|
||||
first = false
|
||||
term.write(value, true)
|
||||
end
|
||||
end
|
||||
@ -323,18 +352,49 @@ end
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
--[[ Primitive command line. ]]
|
||||
local command = ""
|
||||
local keyboardId = 0
|
||||
local lastCommand, command = "", ""
|
||||
local isRunning = false
|
||||
local function commandLineKey(_, char, code)
|
||||
|
||||
event.listen("component_installed", function(_, id)
|
||||
local type = component.type(id)
|
||||
if type == "keyboard" and keyboardId < 1 then
|
||||
keyboardId = id
|
||||
end
|
||||
end)
|
||||
|
||||
event.listen("component_uninstalled", function(_, id)
|
||||
if keyboardId == id then
|
||||
keyboardId = 0
|
||||
for id in component.ids() do
|
||||
if component.type(id) == "keyboard" then
|
||||
keyboardId = id
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
-- Put this into the term table since other programs may want to use it, too.
|
||||
function term.keyboardId(id)
|
||||
if id then
|
||||
checkArg(1, id, "number")
|
||||
keyboardId = id
|
||||
end
|
||||
return keyboardId
|
||||
end
|
||||
|
||||
local function onKeyDown(_, address, char, code)
|
||||
if isRunning then return end -- ignore events while running a command
|
||||
local keys = driver.keyboard.keys
|
||||
local gpu = term.gpu()
|
||||
if component.id(address) ~= keyboardId then return end
|
||||
if not boundGpu then return end
|
||||
local x, y = term.getCursor()
|
||||
local keys = driver.keyboard.keys
|
||||
if code == keys.back then
|
||||
if command:len() == 0 then return end
|
||||
command = command:sub(1, -2)
|
||||
term.setCursor(command:len() + 3, y) -- from leading "> "
|
||||
gpu.set(x - 1, y, " ") -- overwrite cursor blink
|
||||
boundGpu.set(x - 1, y, " ") -- overwrite cursor blink
|
||||
elseif code == keys.enter then
|
||||
if command:len() == 0 then return end
|
||||
print(" ") -- overwrite cursor blink
|
||||
@ -347,14 +407,19 @@ local function commandLineKey(_, char, code)
|
||||
local result = {pcall(code)}
|
||||
isRunning = false
|
||||
if not result[1] or result[2] ~= nil then
|
||||
-- TODO handle multiple results?
|
||||
print(result[2])
|
||||
print(table.unpack(result, 2))
|
||||
end
|
||||
else
|
||||
print(result)
|
||||
end
|
||||
lastCommand = command
|
||||
command = ""
|
||||
write("> ")
|
||||
elseif code == keys.up then
|
||||
command = lastCommand
|
||||
term.clearLine()
|
||||
term.write("> " .. command)
|
||||
term.setCursor(command:len() + 3, y)
|
||||
elseif not keys.isControl(char) then
|
||||
-- Non-control character, add to command.
|
||||
char = string.char(char)
|
||||
@ -362,8 +427,10 @@ local function commandLineKey(_, char, code)
|
||||
term.write(char)
|
||||
end
|
||||
end
|
||||
local function commandLineClipboard(_, value)
|
||||
|
||||
local function onClipboard(_, address, value)
|
||||
if isRunning then return end -- ignore events while running a command
|
||||
if component.id(address) ~= keyboardId then return end
|
||||
value = value:match("([^\r\n]+)")
|
||||
if value and value:len() > 0 then
|
||||
command = command .. value
|
||||
@ -376,12 +443,12 @@ event.listen("term_available", function()
|
||||
term.clear()
|
||||
command = ""
|
||||
write("> ")
|
||||
event.listen("key_down", commandLineKey)
|
||||
event.listen("clipboard", commandLineClipboard)
|
||||
event.listen("key_down", onKeyDown)
|
||||
event.listen("clipboard", onClipboard)
|
||||
end)
|
||||
event.listen("term_unavailable", function()
|
||||
event.ignore("key_down", commandLineKey)
|
||||
event.ignore("clipboard", commandLineClipboard)
|
||||
event.ignore("key_down", onKeyDown)
|
||||
event.ignore("clipboard", onClipboard)
|
||||
end)
|
||||
|
||||
-- Serves as main event loop while keeping the cursor blinking. As soon as
|
||||
|
@ -112,7 +112,8 @@ local sandbox = {
|
||||
difftime = os.difftime,
|
||||
time = os.time,
|
||||
freeMemory = os.freeMemory,
|
||||
totalMemory = function() return os.totalMemory() - os.romSize() end
|
||||
totalMemory = function() return os.totalMemory() - os.romSize() end,
|
||||
address = os.address
|
||||
},
|
||||
|
||||
string = {
|
||||
@ -216,6 +217,16 @@ function sandbox.os.signal(name, timeout)
|
||||
end
|
||||
end
|
||||
|
||||
--[[ Shutdown the computer. ]]
|
||||
function sandbox.os.shutdown()
|
||||
coroutine.yield(false)
|
||||
end
|
||||
|
||||
--[[ Reboot the computer. ]]
|
||||
function sandbox.os.reboot()
|
||||
coroutine.yield(true)
|
||||
end
|
||||
|
||||
-- JNLua converts the coroutine to a string immediately, so we can't get the
|
||||
-- traceback later. Because of that we have to do the error handling here.
|
||||
return pcall(function()
|
||||
|
@ -96,7 +96,10 @@ trait INetwork {
|
||||
def remove(node: INetworkNode): Boolean
|
||||
|
||||
/**
|
||||
* Get the network node with the specified address.
|
||||
* Get the valid network node with the specified address.
|
||||
* <p/>
|
||||
* This does not include nodes with an address less or equal to zero or with
|
||||
* a visibility of `Visibility.None`.
|
||||
* <p/>
|
||||
* If there are multiple nodes with the same address this will return the
|
||||
* node that most recently joined the network.
|
||||
@ -107,18 +110,35 @@ trait INetwork {
|
||||
def node(address: Int): Option[INetworkNode]
|
||||
|
||||
/**
|
||||
* The list of nodes in this network.
|
||||
* The list of all valid nodes in this network.
|
||||
* <p/>
|
||||
* This can be used to perform a delayed initialization of a node. For
|
||||
* example, computers will use this when starting up to generate component
|
||||
* added events for all nodes.
|
||||
* This does not include nodes with an address less or equal to zero or with
|
||||
* a visibility of `Visibility.None`.
|
||||
*
|
||||
* @return the list of nodes in this network.
|
||||
*/
|
||||
def nodes: Iterable[INetworkNode]
|
||||
|
||||
/**
|
||||
* The list of nodes the specified node is directly connected to.
|
||||
* The list of nodes in the network visible to the specified node.
|
||||
* <p/>
|
||||
* The same base filters as for `nodes` apply, with additional visibility
|
||||
* checks applied, based on the specified node as a point of reference.
|
||||
* <p/>
|
||||
* This can be used to perform a delayed initialization of a node. For
|
||||
* example, computers will use this when starting up to generate component
|
||||
* added events for all nodes.
|
||||
*
|
||||
* @param reference the node to get the visible other nodes for.
|
||||
* @return the nodes visible to the specified node.
|
||||
*/
|
||||
def nodes(reference: INetworkNode): Iterable[INetworkNode]
|
||||
|
||||
/**
|
||||
* The list of valid nodes the specified node is directly connected to.
|
||||
* <p/>
|
||||
* This does not include nodes with an address less or equal to zero or with
|
||||
* a visibility of `Visibility.None`.
|
||||
* <p/>
|
||||
* This can be used to verify arguments for components that should only work
|
||||
* for other components that are directly connected to them, for example.
|
||||
@ -130,7 +150,11 @@ trait INetwork {
|
||||
def neighbors(node: INetworkNode): Iterable[INetworkNode]
|
||||
|
||||
/**
|
||||
* Sends a message to a specific node.
|
||||
* Sends a message to a specific address, which may mean multiple nodes.
|
||||
* <p/>
|
||||
* If the target is less or equal to zero no message is sent. If a node with
|
||||
* that address has a visibility of `Visibility.None` the message will not be
|
||||
* delivered to that node.
|
||||
* <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
|
||||
@ -140,8 +164,9 @@ trait INetwork {
|
||||
* <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
|
||||
* more than one target node (shared addresses) the last result that was not
|
||||
* `None` will be returned, or `None` if all were.
|
||||
* more than one target node (shared addresses, should not happen, but may if
|
||||
* a node implementation decides to ignore this rule) the last result that
|
||||
* was not `None` will be returned, or `None` if all results were `None`.
|
||||
*
|
||||
* @param source the node that sends the message.
|
||||
* @param target the id of the node to send the message to.
|
||||
@ -149,10 +174,36 @@ trait INetwork {
|
||||
* @param data the message to send.
|
||||
* @return the result of the message being handled, if any.
|
||||
*/
|
||||
def sendToNode(source: INetworkNode, target: Int, name: String, data: Any*): Option[Array[Any]]
|
||||
def sendToAddress(source: INetworkNode, target: Int, name: String, data: Any*): Option[Array[Any]]
|
||||
|
||||
/**
|
||||
* Sends a message to all nodes in the network.
|
||||
* Sends a message to all direct valid neighbors of the source node.
|
||||
* <p/>
|
||||
* This does not include nodes with an address less or equal to zero or with
|
||||
* a visibility of `Visibility.None`.
|
||||
* <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 "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 name the name of the message.
|
||||
* @param data the message to send.
|
||||
* @see neighbors
|
||||
*/
|
||||
def sendToNeighbors(source: INetworkNode, name: String, data: Any*)
|
||||
|
||||
/**
|
||||
* Sends a message to all valid nodes in the network.
|
||||
* <p/>
|
||||
* This does not include nodes with an address less or equal to zero or with
|
||||
* a visibility of `Visibility.None`.
|
||||
* <p/>
|
||||
* This ignores any further visibility checks, i.e. even if a node is not
|
||||
* visible to the source node it will still receive the message, as long as
|
||||
* it is a valid node.
|
||||
* <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
|
||||
|
@ -35,6 +35,21 @@ trait INetworkNode {
|
||||
*/
|
||||
def name: String
|
||||
|
||||
/**
|
||||
* The visibility of this node.
|
||||
* <p/>
|
||||
* This is used by the network to control which system messages to deliver to
|
||||
* which nodes. This value should not change over the lifetime of a node.
|
||||
* Note that this has no effect on the real reachability of a node; it is
|
||||
* only used to filter to which nodes to send connect, disconnect and
|
||||
* reconnect messages. If addressed directly or when a broadcast is sent, the
|
||||
* node will still receive that message. Therefore nodes should still verify
|
||||
* themselves that they want to accept a message from the message's source.
|
||||
*
|
||||
* @return visibility of the node.
|
||||
*/
|
||||
def visibility = Visibility.None
|
||||
|
||||
/**
|
||||
* The address of the node, so that it can be found in the network.
|
||||
* <p/>
|
||||
|
19
li/cil/oc/api/Visibility.scala
Normal file
19
li/cil/oc/api/Visibility.scala
Normal file
@ -0,0 +1,19 @@
|
||||
package li.cil.oc.api
|
||||
|
||||
/**
|
||||
* Possible reachability values foe nodes.
|
||||
* <p/>
|
||||
* Since all components that are connected are packed into the same network,
|
||||
* we want some way of controlling what's accessible from where on a low
|
||||
* level (to avoid unnecessary messages and unauthorized access).
|
||||
*/
|
||||
object Visibility extends Enumeration {
|
||||
/** The node neither receives nor sends messages. */
|
||||
val None = Value("None")
|
||||
|
||||
/** The node only handles messages from its immediate neighbors. */
|
||||
val Neighbors = Value("Neighbors")
|
||||
|
||||
/** The node can interact with the complete network. */
|
||||
val Network = Value("Network")
|
||||
}
|
@ -29,16 +29,16 @@ class GuiScreen(val tileEntity: TileEntityScreen) extends MCGuiScreen {
|
||||
/** Must be called when the size of the underlying screen changes */
|
||||
def setSize(w: Double, h: Double) = {
|
||||
// Re-compute sizes and positions.
|
||||
val totalMargin = (GuiScreen.margin + GuiScreen.innerMargin) * 2
|
||||
val totalMargin = GuiScreen.margin + GuiScreen.innerMargin
|
||||
val bufferWidth = w * MonospaceFontRenderer.fontWidth
|
||||
val bufferHeight = h * MonospaceFontRenderer.fontHeight
|
||||
val bufferScaleX = ((width - totalMargin) / bufferWidth) min 1
|
||||
val bufferScaleY = ((height - totalMargin) / bufferHeight) min 1
|
||||
val bufferScaleX = (width / (bufferWidth + totalMargin * 2.0)) min 1
|
||||
val bufferScaleY = (height / (bufferHeight + totalMargin * 2.0)) min 1
|
||||
scale = bufferScaleX min bufferScaleY
|
||||
innerWidth = (bufferWidth * scale + 1).ceil.toInt
|
||||
innerHeight = (bufferHeight * scale + 1).ceil.toInt
|
||||
x = (width - (innerWidth + totalMargin)) / 2
|
||||
y = (height - (innerHeight + totalMargin)) / 2
|
||||
innerWidth = (bufferWidth * scale).toInt
|
||||
innerHeight = (bufferHeight * scale).toInt
|
||||
x = (width - (innerWidth + totalMargin * 2)) / 2
|
||||
y = (height - (innerHeight + totalMargin * 2)) / 2
|
||||
|
||||
// Re-build display lists.
|
||||
GuiScreen.compileBackground(innerWidth, innerHeight)
|
||||
@ -114,43 +114,46 @@ object GuiScreen {
|
||||
GL11.glCallLists(buffer.get)
|
||||
}
|
||||
|
||||
private[gui] def compileBackground(innerWidth: Int, innerHeight: Int) =
|
||||
private[gui] def compileBackground(bufferWidth: Int, bufferHeight: Int) =
|
||||
if (textureManager.isDefined) {
|
||||
val innerWidth = innerMargin * 2 + bufferWidth
|
||||
val innerHeight = innerMargin * 2 + bufferHeight
|
||||
|
||||
GL11.glNewList(displayLists.get, GL11.GL_COMPILE)
|
||||
|
||||
setTexture(borders)
|
||||
|
||||
// Top border (left corner, middle bar, right corner).
|
||||
drawBorder(
|
||||
0, 0, 7, 7,
|
||||
0, 0, margin, margin,
|
||||
0, 0, 7, 7)
|
||||
drawBorder(
|
||||
margin, 0, innerWidth, 7,
|
||||
margin, 0, innerWidth, margin,
|
||||
7, 0, 8, 7)
|
||||
drawBorder(
|
||||
margin + innerWidth, 0, 7, 7,
|
||||
margin + innerWidth, 0, margin, margin,
|
||||
8, 0, 15, 7)
|
||||
|
||||
// Middle area (left bar, screen background, right bar).
|
||||
drawBorder(
|
||||
0, margin, 7, innerHeight,
|
||||
0, margin, margin, innerHeight,
|
||||
0, 7, 7, 8)
|
||||
drawBorder(
|
||||
margin, margin, innerWidth, innerHeight,
|
||||
7, 7, 8, 8)
|
||||
drawBorder(
|
||||
margin + innerWidth, margin, 7, innerHeight,
|
||||
margin + innerWidth, margin, margin, innerHeight,
|
||||
8, 7, 15, 8)
|
||||
|
||||
// Bottom border (left corner, middle bar, right corner).
|
||||
drawBorder(
|
||||
0, margin + innerHeight, 7, 7,
|
||||
0, margin + innerHeight, margin, margin,
|
||||
0, 8, 7, 15)
|
||||
drawBorder(
|
||||
margin, margin + innerHeight, innerWidth, 7,
|
||||
margin, margin + innerHeight, innerWidth, margin,
|
||||
7, 8, 8, 15)
|
||||
drawBorder(
|
||||
margin + innerWidth, margin + innerHeight, 7, 7,
|
||||
margin + innerWidth, margin + innerHeight, margin, margin,
|
||||
8, 8, 15, 15)
|
||||
|
||||
GL11.glEndList()
|
||||
|
@ -55,10 +55,7 @@ class BlockMulti(id: Int) extends Block(id, Material.iron) {
|
||||
}
|
||||
|
||||
def subBlock(world: IBlockAccess, x: Int, y: Int, z: Int): Option[SubBlock] =
|
||||
subBlock(world.getBlockMetadata(x, y, z)) match {
|
||||
case Some(subBlock) if world.getBlockId(x, y, z) == this.blockID => Some(subBlock)
|
||||
case _ => None
|
||||
}
|
||||
subBlock(world.getBlockMetadata(x, y, z))
|
||||
|
||||
def subBlock(metadata: Int) =
|
||||
metadata match {
|
||||
|
@ -1,6 +1,6 @@
|
||||
package li.cil.oc.common.components
|
||||
|
||||
import li.cil.oc.api.{INetworkMessage, INetworkNode}
|
||||
import li.cil.oc.api.{Visibility, INetworkMessage, INetworkNode}
|
||||
import net.minecraft.nbt.NBTTagCompound
|
||||
|
||||
/**
|
||||
@ -15,6 +15,8 @@ trait IScreenEnvironment extends INetworkNode {
|
||||
|
||||
override def name = "screen"
|
||||
|
||||
override def visibility = Visibility.Neighbors
|
||||
|
||||
override def receive(message: INetworkMessage): Option[Array[Any]] = {
|
||||
super.receive(message)
|
||||
message.data match {
|
||||
@ -51,7 +53,7 @@ trait IScreenEnvironment extends INetworkNode {
|
||||
}
|
||||
|
||||
def onScreenResolutionChange(w: Int, h: Int) = if (network != null) {
|
||||
network.sendToAll(this, "computer.signal", "screen_resized", this.address, w, h)
|
||||
network.sendToAll(this, "computer.signal", "screen_resized", w, h)
|
||||
}
|
||||
|
||||
def onScreenSet(col: Int, row: Int, s: String) {}
|
||||
|
@ -59,7 +59,7 @@ class ItemMulti(id: Int) extends Item(id) {
|
||||
override def getUnlocalizedName(item: ItemStack): String =
|
||||
subItem(item) match {
|
||||
case None => getUnlocalizedName
|
||||
case Some(subItem) => subItem.unlocalizedName
|
||||
case Some(subItem) => "oc.item." + subItem.unlocalizedName
|
||||
}
|
||||
|
||||
override def getUnlocalizedName: String = "oc.item"
|
||||
|
@ -30,16 +30,22 @@ class TileEntityComputer(isClient: Boolean) extends TileEntityRotatable with ICo
|
||||
override def receive(message: INetworkMessage) = {
|
||||
super.receive(message)
|
||||
message.data match {
|
||||
// The isRunning check is here to avoid network.connect messages being sent
|
||||
// while loading a chunk (thus leading to "false" component_added signals).
|
||||
case Array() if message.name == "network.connect" && isRunning =>
|
||||
// The isRunning check is here to avoid component_* signals being
|
||||
// generated while loading a chunk.
|
||||
case Array() if message.name == "network.connect" && message.source.address != 0 && isRunning =>
|
||||
computer.signal("component_added", message.source.address); None
|
||||
case Array() if message.name == "network.disconnect" && isRunning =>
|
||||
case Array() if message.name == "network.disconnect" && message.source.address != 0 && isRunning =>
|
||||
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 == "computer.signal" =>
|
||||
computer.signal(name, args: _*); None
|
||||
computer.signal(name, List(message.source.address) ++ args: _*); None
|
||||
case Array() if message.name == "computer.start" =>
|
||||
Some(Array(turnOn().asInstanceOf[Any]))
|
||||
case Array() if message.name == "computer.stop" =>
|
||||
Some(Array(turnOff().asInstanceOf[Any]))
|
||||
case Array() if message.name == "computer.running" =>
|
||||
Some(Array(isOn.asInstanceOf[Any]))
|
||||
case _ => None
|
||||
}
|
||||
}
|
||||
@ -92,9 +98,9 @@ class TileEntityComputer(isClient: Boolean) extends TileEntityRotatable with ICo
|
||||
isRunning = computer.isRunning
|
||||
if (network != null)
|
||||
if (isRunning)
|
||||
network.sendToAll(this, "computer.start")
|
||||
network.sendToAll(this, "computer.started")
|
||||
else
|
||||
network.sendToAll(this, "computer.stop")
|
||||
network.sendToAll(this, "computer.stopped")
|
||||
ServerPacketSender.sendComputerState(this, isRunning)
|
||||
}
|
||||
}
|
||||
|
@ -1,28 +1,27 @@
|
||||
package li.cil.oc.common.tileentity
|
||||
|
||||
import cpw.mods.fml.common.network.Player
|
||||
import li.cil.oc.api.{INetworkNode, INetworkMessage}
|
||||
import li.cil.oc.api.{Visibility, INetworkNode, INetworkMessage}
|
||||
import net.minecraft.entity.player.EntityPlayer
|
||||
import net.minecraft.nbt.NBTTagCompound
|
||||
|
||||
class TileEntityKeyboard extends TileEntityRotatable with INetworkNode {
|
||||
override def name = "keyboard"
|
||||
|
||||
override def visibility = Visibility.Network
|
||||
|
||||
override def receive(message: INetworkMessage) = {
|
||||
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, "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, "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, "computer.signal", "clipboard", value)
|
||||
message.cancel()
|
||||
}
|
||||
case Array(p: Player, char: Char, code: Int) if message.name == "keyboard.keyDown" =>
|
||||
if (isUseableByPlayer(p))
|
||||
network.sendToAll(this, "computer.signal", "key_down", char, code)
|
||||
case Array(p: Player, char: Char, code: Int) if message.name == "keyboard.keyUp" =>
|
||||
if (isUseableByPlayer(p))
|
||||
network.sendToAll(this, "computer.signal", "key_up", char, code)
|
||||
case Array(p: Player, value: String) if message.name == "keyboard.clipboard" =>
|
||||
if (isUseableByPlayer(p))
|
||||
network.sendToAll(this, "computer.signal", "clipboard", value)
|
||||
case _ => // Ignore.
|
||||
}
|
||||
None
|
||||
|
@ -68,18 +68,18 @@ class PacketHandler extends CommonPacketHandler {
|
||||
def onKeyDown(p: PacketParser) =
|
||||
p.readTileEntity[INetworkNode]() match {
|
||||
case None => // Invalid packet.
|
||||
case Some(n) => n.network.sendToAll(n, "keyboard.keyDown", p.player, p.readChar(), p.readInt())
|
||||
case Some(n) => n.network.sendToNeighbors(n, "keyboard.keyDown", p.player, p.readChar(), p.readInt())
|
||||
}
|
||||
|
||||
def onKeyUp(p: PacketParser) =
|
||||
p.readTileEntity[INetworkNode]() match {
|
||||
case None => // Invalid packet.
|
||||
case Some(n) => n.network.sendToAll(n, "keyboard.keyUp", p.player, p.readChar(), p.readInt())
|
||||
case Some(n) => n.network.sendToNeighbors(n, "keyboard.keyUp", p.player, p.readChar(), p.readInt())
|
||||
}
|
||||
|
||||
def onClipboard(p: PacketParser) =
|
||||
p.readTileEntity[INetworkNode]() match {
|
||||
case None => // Invalid packet.
|
||||
case Some(n) => n.network.sendToAll(n, "keyboard.clipboard", p.player, p.readUTF())
|
||||
}
|
||||
p.readTileEntity[INetworkNode]() match {
|
||||
case None => // Invalid packet.
|
||||
case Some(n) => n.network.sendToNeighbors(n, "keyboard.clipboard", p.player, p.readUTF())
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
package li.cil.oc.server.components
|
||||
|
||||
import li.cil.oc.api.{INetworkNode, INetworkMessage}
|
||||
import li.cil.oc.api.INetworkMessage
|
||||
import net.minecraft.nbt.NBTTagCompound
|
||||
|
||||
class GraphicsCard(nbt: NBTTagCompound) extends ItemComponent(nbt) {
|
||||
@ -13,24 +13,24 @@ class GraphicsCard(nbt: NBTTagCompound) extends ItemComponent(nbt) {
|
||||
message.data match {
|
||||
case Array(screen: Double, w: Double, h: Double) if message.name == "gpu.resolution=" =>
|
||||
if (supportedResolutions.contains((w.toInt, h.toInt)))
|
||||
network.sendToNode(message.source, screen.toInt, "screen.resolution=", w.toInt, h.toInt)
|
||||
network.sendToAddress(this, screen.toInt, "screen.resolution=", w.toInt, h.toInt)
|
||||
else Some(Array(None, "unsupported resolution"))
|
||||
case Array(screen: Double) if message.name == "gpu.resolution" =>
|
||||
network.sendToNode(message.source, screen.toInt, "screen.resolution")
|
||||
network.sendToAddress(this, screen.toInt, "screen.resolution")
|
||||
case Array(screen: Double) if message.name == "gpu.resolutions" =>
|
||||
network.sendToNode(this, screen.toInt, "screen.resolutions") match {
|
||||
network.sendToAddress(this, screen.toInt, "screen.resolutions") match {
|
||||
case Some(Array(resolutions@_*)) =>
|
||||
Some(Array(supportedResolutions.intersect(resolutions): _*))
|
||||
case _ => None
|
||||
}
|
||||
case Array(screen: Double, x: Double, y: Double, value: String) if message.name == "gpu.set" =>
|
||||
network.sendToNode(this, screen.toInt, "screen.set", x.toInt - 1, y.toInt - 1, value)
|
||||
network.sendToAddress(this, screen.toInt, "screen.set", x.toInt - 1, y.toInt - 1, value)
|
||||
case Array(screen: Double, x: Double, y: Double, w: Double, h: Double, value: String) if message.name == "gpu.fill" =>
|
||||
if (value != null && value.length == 1)
|
||||
network.sendToNode(this, screen.toInt, "screen.fill", x.toInt - 1, y.toInt - 1, w.toInt, h.toInt, value.charAt(0))
|
||||
network.sendToAddress(this, screen.toInt, "screen.fill", x.toInt - 1, y.toInt - 1, w.toInt, h.toInt, value.charAt(0))
|
||||
else Some(Array(None, "invalid fill value"))
|
||||
case Array(screen: Double, x: Double, y: Double, w: Double, h: Double, tx: Double, ty: Double) if message.name == "gpu.copy" =>
|
||||
network.sendToNode(this, screen.toInt, "screen.copy", x.toInt - 1, y.toInt - 1, w.toInt, h.toInt, tx.toInt, ty.toInt)
|
||||
network.sendToAddress(this, screen.toInt, "screen.copy", x.toInt - 1, y.toInt - 1, w.toInt, h.toInt, tx.toInt, ty.toInt)
|
||||
case _ => None
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,13 @@
|
||||
package li.cil.oc.server.components
|
||||
|
||||
import li.cil.oc.api.INetworkNode
|
||||
import li.cil.oc.api.{Visibility, INetworkNode}
|
||||
import net.minecraft.nbt.NBTTagCompound
|
||||
|
||||
abstract class ItemComponent(val nbt: NBTTagCompound) extends INetworkNode {
|
||||
address = nbt.getInteger("address")
|
||||
|
||||
override def visibility = Visibility.Neighbors
|
||||
|
||||
override def address_=(value: Int) = {
|
||||
super.address_=(value)
|
||||
nbt.setInteger("address", address)
|
||||
|
@ -1,6 +1,7 @@
|
||||
package li.cil.oc.server.computer
|
||||
|
||||
import com.naef.jnlua._
|
||||
import java.lang.Thread.UncaughtExceptionHandler
|
||||
import java.util.concurrent._
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import java.util.logging.Level
|
||||
@ -61,8 +62,11 @@ class Computer(val owner: IComputerEnvironment) extends IComputer with Runnable
|
||||
* the Java side and processed one by one in the Lua VM. They are the only
|
||||
* means to communicate actively with the computer (passively only message
|
||||
* handlers can interact with the computer by returning some result).
|
||||
* <p/>
|
||||
* The queue is intentionally pretty big, because we have to enqueue one
|
||||
* signal for for each component in the network when the computer starts up.
|
||||
*/
|
||||
private val signals = new LinkedBlockingQueue[Computer.Signal](100)
|
||||
private val signals = new LinkedBlockingQueue[Computer.Signal](256)
|
||||
|
||||
// ----------------------------------------------------------------------- //
|
||||
|
||||
@ -71,7 +75,7 @@ class Computer(val owner: IComputerEnvironment) extends IComputer with Runnable
|
||||
* custom implementation of os.clock(), which returns the amount of the time
|
||||
* the computer has been running.
|
||||
*/
|
||||
private var timeStarted = 0.0
|
||||
private var timeStarted = 0L
|
||||
|
||||
/**
|
||||
* The last time (system time) the update function was called by the server
|
||||
@ -95,18 +99,22 @@ class Computer(val owner: IComputerEnvironment) extends IComputer with Runnable
|
||||
*/
|
||||
private var future: Option[Future[_]] = None
|
||||
|
||||
/**
|
||||
* Timestamp until which to sleep, i.e. when we hit this time we will create
|
||||
* a future to run the computer. Until then we have nothing to do.
|
||||
*/
|
||||
private var sleepUntil = Long.MaxValue
|
||||
|
||||
/** This is used to synchronize access to the state field. */
|
||||
private val stateMonitor = new Object()
|
||||
|
||||
/** This is used to synchronize while saving, so we don't stop while we do. */
|
||||
private val saveMonitor = new Object()
|
||||
|
||||
// ----------------------------------------------------------------------- //
|
||||
// IComputerContext
|
||||
// ----------------------------------------------------------------------- //
|
||||
|
||||
override def signal(name: String, args: Any*) = {
|
||||
def values = args.map {
|
||||
override def signal(name: String, args: Any*) = stateMonitor.synchronized(state match {
|
||||
case Computer.State.Stopped | Computer.State.Stopping => false
|
||||
case _ => signals.offer(new Computer.Signal(name, args.map {
|
||||
case null | Unit => Unit
|
||||
case arg: Boolean => arg
|
||||
case arg: Byte => arg.toDouble
|
||||
@ -118,263 +126,253 @@ class Computer(val owner: IComputerEnvironment) extends IComputer with Runnable
|
||||
case arg: Double => arg
|
||||
case arg: String => arg
|
||||
case _ => throw new IllegalArgumentException()
|
||||
}.toArray
|
||||
stateMonitor.synchronized(state match {
|
||||
// We don't push new signals when stopped or shutting down.
|
||||
case Computer.State.Stopped | Computer.State.Stopping => false
|
||||
// Currently sleeping. Cancel that and start immediately.
|
||||
case Computer.State.Sleeping =>
|
||||
val v = values // Map first, may error.
|
||||
future.get.cancel(true)
|
||||
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 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 Computer.Signal(name, values))
|
||||
true
|
||||
})
|
||||
}
|
||||
}.toArray))
|
||||
})
|
||||
|
||||
// ----------------------------------------------------------------------- //
|
||||
// IComputer
|
||||
// ----------------------------------------------------------------------- //
|
||||
|
||||
override def start() = stateMonitor.synchronized(
|
||||
state == Computer.State.Stopped && init() && {
|
||||
(state == Computer.State.Stopped) && init() && {
|
||||
// Initial state. Will be switched to State.Yielded in the next update()
|
||||
// due to the signals queue not being empty (
|
||||
state = Computer.State.Suspended
|
||||
|
||||
// Remember when we started, for os.clock().
|
||||
timeStarted = owner.world.getWorldInfo.getWorldTotalTime
|
||||
|
||||
// Mark state change in owner, to send it to clients.
|
||||
owner.markAsChanged()
|
||||
|
||||
// Inject a dummy signal so that real ones don't get swallowed. This way
|
||||
// we can just ignore the parameters the first time the kernel is run
|
||||
// and all actual signals will be read using coroutine.yield().
|
||||
// IMPORTANT: This will also create our worker thread for the first run.
|
||||
signal("dummy")
|
||||
signal("")
|
||||
|
||||
// Inject component added signals for all nodes in the network.
|
||||
owner.network.nodes.foreach(node => signal("component_added", node.address))
|
||||
owner.network.nodes(owner).foreach(node => signal("component_added", node.address))
|
||||
|
||||
// All green, computer started successfully.
|
||||
true
|
||||
})
|
||||
|
||||
override def stop() = saveMonitor.synchronized(stateMonitor.synchronized {
|
||||
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.
|
||||
future.foreach(_.cancel(true))
|
||||
close()
|
||||
}
|
||||
else {
|
||||
// Otherwise we enter an intermediate state to ensure the executor
|
||||
// truly stopped, before switching back to stopped to allow starting
|
||||
// the computer again. The executor will check for this state and
|
||||
// call close.
|
||||
state = Computer.State.Stopping
|
||||
}
|
||||
override def stop() = stateMonitor.synchronized(state match {
|
||||
case Computer.State.Stopped => false // Nothing to do.
|
||||
case _ if future.isEmpty => close(); true // Not executing, kill it.
|
||||
case _ =>
|
||||
// If the computer is currently executing something we enter an
|
||||
// intermediate state to ensure the executor or synchronized call truly
|
||||
// stopped, before switching back to stopped to allow starting the
|
||||
// computer again. The executor and synchronized call will check for
|
||||
// this state and call close(), thus switching the state to stopped.
|
||||
state = Computer.State.Stopping
|
||||
true
|
||||
}
|
||||
else false
|
||||
})
|
||||
|
||||
override def isRunning = stateMonitor.synchronized(state != Computer.State.Stopped)
|
||||
override def isRunning = state != Computer.State.Stopped
|
||||
|
||||
override def update() {
|
||||
stateMonitor.synchronized(state match {
|
||||
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 = Computer.State.SynchronizedReturn
|
||||
assert(!future.isDefined)
|
||||
future = Some(Computer.Executor.pool.submit(this))
|
||||
} catch {
|
||||
// This can happen if we run out of memory while converting a Java exception to a string.
|
||||
case _: LuaMemoryAllocationException =>
|
||||
// TODO error message somewhere ingame
|
||||
close()
|
||||
// This should not happen.
|
||||
case _: Throwable => {
|
||||
OpenComputers.log.warning("Faulty Lua implementation for synchronized calls.")
|
||||
close()
|
||||
}
|
||||
}
|
||||
}
|
||||
case Computer.State.Paused => {
|
||||
state = Computer.State.Suspended
|
||||
assert(!future.isDefined)
|
||||
future = Some(Computer.Executor.pool.submit(this))
|
||||
}
|
||||
case Computer.State.SynchronizedReturnPaused => {
|
||||
state = Computer.State.SynchronizedReturn
|
||||
assert(!future.isDefined)
|
||||
future = Some(Computer.Executor.pool.submit(this))
|
||||
}
|
||||
case _ => /* Nothing special to do. */
|
||||
})
|
||||
// Update last time run to let our executor thread know it doesn't have to
|
||||
// pause.
|
||||
lastUpdate = System.currentTimeMillis
|
||||
|
||||
// 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 = worldTime
|
||||
|
||||
// Update last time run to let our executor thread know it doesn't have to
|
||||
// pause.
|
||||
lastUpdate = System.currentTimeMillis
|
||||
// Check if we should switch states.
|
||||
stateMonitor.synchronized(state match {
|
||||
// Resume from pauses based on signal underflow.
|
||||
case Computer.State.Suspended if signals.nonEmpty => {
|
||||
assert(future.isEmpty)
|
||||
execute(Computer.State.Yielded)
|
||||
}
|
||||
case Computer.State.Sleeping if lastUpdate >= sleepUntil || signals.nonEmpty => {
|
||||
assert(future.isEmpty)
|
||||
execute(Computer.State.Yielded)
|
||||
}
|
||||
// Resume in case we paused because the game was paused.
|
||||
case Computer.State.Paused => {
|
||||
assert(future.isEmpty)
|
||||
execute(Computer.State.Yielded)
|
||||
}
|
||||
case Computer.State.SynchronizedReturnPaused => {
|
||||
assert(future.isEmpty)
|
||||
execute(Computer.State.SynchronizedReturn)
|
||||
}
|
||||
// Perform a synchronized call (message sending).
|
||||
case Computer.State.SynchronizedCall => {
|
||||
assert(future.isEmpty)
|
||||
// These three asserts are all guaranteed by run().
|
||||
assert(lua.getTop == 2)
|
||||
assert(lua.isThread(1))
|
||||
assert(lua.isFunction(2))
|
||||
// We switch into running state, since we'll behave as though the call
|
||||
// were performed from our executor thread.
|
||||
state = Computer.State.Running
|
||||
try {
|
||||
// Synchronized call protocol requires the called function to return
|
||||
// a table, which holds the results of the call, to be passed back
|
||||
// to the coroutine.yield() that triggered the call.
|
||||
lua.call(0, 1)
|
||||
lua.checkType(2, LuaType.TABLE)
|
||||
} catch {
|
||||
case _: LuaMemoryAllocationException =>
|
||||
// This can happen if we run out of memory while converting a Java
|
||||
// exception to a string (which we have to do to avoid keeping
|
||||
// userdata on the stack, which cannot be persisted).
|
||||
OpenComputers.log.warning("Out of memory!") // TODO remove this when we have a component that can display crash messages
|
||||
owner.network.sendToAll(owner, "computer.crashed", "not enough memory")
|
||||
close()
|
||||
case e: Throwable => {
|
||||
OpenComputers.log.log(Level.WARNING, "Faulty Lua implementation for synchronized calls.", e)
|
||||
close()
|
||||
}
|
||||
}
|
||||
// Nothing should have been able to trigger a future.
|
||||
assert(future.isEmpty)
|
||||
// If the call lead to stop() being called we stop right now,
|
||||
// otherwise we return the result to our executor.
|
||||
if (state == Computer.State.Stopping)
|
||||
close()
|
||||
else
|
||||
execute(Computer.State.SynchronizedReturn)
|
||||
}
|
||||
case _ => // Nothing special to do, just avoid match errors.
|
||||
})
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------- //
|
||||
|
||||
override def load(nbt: NBTTagCompound): Unit =
|
||||
saveMonitor.synchronized(this.synchronized {
|
||||
// Clear out what we currently have, if anything.
|
||||
stateMonitor.synchronized {
|
||||
assert(state != Computer.State.Running) // Lock on 'this' should guarantee this.
|
||||
stop()
|
||||
}
|
||||
override def load(nbt: NBTTagCompound) {
|
||||
state = nbt.getInteger("state") match {
|
||||
case id if id >= 0 && id < Computer.State.maxId => Computer.State(id)
|
||||
case _ => Computer.State.Stopped
|
||||
}
|
||||
|
||||
state = Computer.State(nbt.getInteger("state"))
|
||||
|
||||
if (state != Computer.State.Stopped && init()) {
|
||||
// Unlimit memory use while unpersisting.
|
||||
val memory = lua.getTotalMemory
|
||||
lua.setTotalMemory(Integer.MAX_VALUE)
|
||||
try {
|
||||
// Try unpersisting Lua, because that's what all of the rest depends
|
||||
// on. First, clear the stack, meaning the current kernel.
|
||||
lua.setTop(0)
|
||||
|
||||
if (!unpersist(nbt.getByteArray("kernel")) || !lua.isThread(1)) {
|
||||
// This shouldn't really happen, but there's a chance it does if
|
||||
// the save was corrupt (maybe someone modified the Lua files).
|
||||
throw new IllegalStateException("Could not restore kernel.")
|
||||
}
|
||||
if (state == Computer.State.SynchronizedCall || state == Computer.State.SynchronizedReturn) {
|
||||
if (!unpersist(nbt.getByteArray("stack")) ||
|
||||
(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.")
|
||||
}
|
||||
assert(lua.getTop == 2)
|
||||
}
|
||||
|
||||
assert(signals.size() == 0)
|
||||
val signalsTag = nbt.getTagList("signals")
|
||||
signals.addAll((0 until signalsTag.tagCount()).
|
||||
map(signalsTag.tagAt(_).asInstanceOf[NBTTagCompound]).
|
||||
map(signal => {
|
||||
val argsTag = signal.getCompoundTag("args")
|
||||
val argsLength = argsTag.getInteger("length")
|
||||
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
|
||||
case tag: NBTTagDouble => tag.data
|
||||
case tag: NBTTagString => tag.data
|
||||
}.toArray)
|
||||
}).asJava)
|
||||
|
||||
timeStarted = nbt.getDouble("timeStarted")
|
||||
|
||||
// Clean up some after we're done and limit memory again.
|
||||
lua.gc(LuaState.GcAction.COLLECT, 0)
|
||||
lua.setTotalMemory(memory)
|
||||
|
||||
// Start running our worker thread.
|
||||
assert(!future.isDefined)
|
||||
state match {
|
||||
case Computer.State.Suspended | Computer.State.Sleeping | Computer.State.SynchronizedReturn =>
|
||||
future = Some(Computer.Executor.pool.submit(this))
|
||||
case _ => // Wasn't running before.
|
||||
}
|
||||
|
||||
} catch {
|
||||
case t: Throwable => {
|
||||
OpenComputers.log.log(Level.WARNING, "Could not restore computer.", t)
|
||||
close()
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
close()
|
||||
}
|
||||
})
|
||||
|
||||
override def save(nbt: NBTTagCompound): Unit =
|
||||
saveMonitor.synchronized(this.synchronized {
|
||||
stateMonitor.synchronized {
|
||||
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 == Computer.State.Stopped) {
|
||||
return
|
||||
}
|
||||
|
||||
// Unlimit memory while persisting.
|
||||
if (state != Computer.State.Stopped && init()) {
|
||||
// Unlimit memory use while unpersisting.
|
||||
val memory = lua.getTotalMemory
|
||||
lua.setTotalMemory(Integer.MAX_VALUE)
|
||||
try {
|
||||
// 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.
|
||||
// Try unpersisting Lua, because that's what all of the rest depends
|
||||
// on. First, clear the stack, meaning the current kernel.
|
||||
lua.setTop(0)
|
||||
|
||||
if (!nbt.hasKey("kernel") || !unpersist(nbt.getByteArray("kernel")) || !lua.isThread(1)) {
|
||||
// This shouldn't really happen, but there's a chance it does if
|
||||
// the save was corrupt (maybe someone modified the Lua files).
|
||||
throw new IllegalStateException("Invalid kernel.")
|
||||
}
|
||||
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).
|
||||
assert(lua.isThread(1))
|
||||
nbt.setByteArray("kernel", persist(1))
|
||||
|
||||
val list = new NBTTagList
|
||||
for (s <- signals.iterator) {
|
||||
val signal = new NBTTagCompound
|
||||
signal.setString("name", s.name)
|
||||
val args = new NBTTagCompound
|
||||
args.setInteger("length", s.args.length)
|
||||
s.args.zipWithIndex.foreach {
|
||||
case (Unit, i) => args.setByte("arg" + i, -1)
|
||||
case (arg: Boolean, i) => args.setByte("arg" + i, if (arg) 1 else 0)
|
||||
case (arg: Double, i) => args.setDouble("arg" + i, arg)
|
||||
case (arg: String, i) => args.setString("arg" + i, arg)
|
||||
if (!nbt.hasKey("stack") || !unpersist(nbt.getByteArray("stack")) ||
|
||||
(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("Invalid stack.")
|
||||
}
|
||||
signal.setCompoundTag("args", args)
|
||||
list.appendTag(signal)
|
||||
}
|
||||
nbt.setTag("signals", list)
|
||||
|
||||
nbt.setDouble("timeStarted", timeStarted)
|
||||
}
|
||||
catch {
|
||||
case t: Throwable => {
|
||||
t.printStackTrace()
|
||||
nbt.setInteger("state", Computer.State.Stopped.id)
|
||||
}
|
||||
}
|
||||
finally {
|
||||
assert(signals.size == 0)
|
||||
val signalsNbt = nbt.getTagList("signals")
|
||||
signals.addAll((0 until signalsNbt.tagCount()).
|
||||
map(signalsNbt.tagAt(_).asInstanceOf[NBTTagCompound]).
|
||||
map(signalNbt => {
|
||||
val argsNbt = signalNbt.getCompoundTag("args")
|
||||
val argsLength = argsNbt.getInteger("length")
|
||||
new Computer.Signal(signalNbt.getString("name"),
|
||||
(0 until argsLength).map("arg" + _).map(argsNbt.getTag).map {
|
||||
case tag: NBTTagByte if tag.data == -1 => Unit
|
||||
case tag: NBTTagByte => tag.data == 1
|
||||
case tag: NBTTagDouble => tag.data
|
||||
case tag: NBTTagString => tag.data
|
||||
case _ => throw new IllegalStateException("Invalid signal.")
|
||||
}.toArray)
|
||||
}).asJava)
|
||||
|
||||
timeStarted = nbt.getLong("timeStarted")
|
||||
|
||||
// Clean up some after we're done and limit memory again.
|
||||
lua.gc(LuaState.GcAction.COLLECT, 0)
|
||||
lua.setTotalMemory(memory)
|
||||
|
||||
// Start running our worker thread if we have to (for cases where it
|
||||
// would not be re-started automatically in update()). We start with a
|
||||
// slight delay, to allow the world to settle.
|
||||
assert(future.isEmpty)
|
||||
state match {
|
||||
case Computer.State.Yielded | Computer.State.SynchronizedReturn =>
|
||||
future = Some(Computer.Executor.pool.schedule(this, 500, TimeUnit.MILLISECONDS))
|
||||
case Computer.State.Sleeping => sleepUntil = Long.MinValue
|
||||
case _ => // Will be started by update() if necessary.
|
||||
}
|
||||
} catch {
|
||||
case e: IllegalStateException => {
|
||||
OpenComputers.log.log(Level.WARNING, "Could not restore computer.", e)
|
||||
close()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
// Init failed, or we were already stopped.
|
||||
else state = Computer.State.Stopped
|
||||
}
|
||||
|
||||
override def save(nbt: NBTTagCompound): Unit = this.synchronized {
|
||||
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 == Computer.State.Stopped) {
|
||||
return
|
||||
}
|
||||
|
||||
// Unlimit memory while persisting.
|
||||
val memory = lua.getTotalMemory
|
||||
lua.setTotalMemory(Integer.MAX_VALUE)
|
||||
try {
|
||||
// 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 == 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).
|
||||
assert(lua.isThread(1))
|
||||
nbt.setByteArray("kernel", persist(1))
|
||||
|
||||
val list = new NBTTagList
|
||||
for (s <- signals.iterator) {
|
||||
val signal = new NBTTagCompound
|
||||
signal.setString("name", s.name)
|
||||
val args = new NBTTagCompound
|
||||
args.setInteger("length", s.args.length)
|
||||
s.args.zipWithIndex.foreach {
|
||||
case (Unit, i) => args.setByte("arg" + i, -1)
|
||||
case (arg: Boolean, i) => args.setByte("arg" + i, if (arg) 1 else 0)
|
||||
case (arg: Double, i) => args.setDouble("arg" + i, arg)
|
||||
case (arg: String, i) => args.setString("arg" + i, arg)
|
||||
}
|
||||
signal.setCompoundTag("args", args)
|
||||
list.appendTag(signal)
|
||||
}
|
||||
nbt.setTag("signals", list)
|
||||
|
||||
nbt.setLong("timeStarted", timeStarted)
|
||||
}
|
||||
catch {
|
||||
case e: Throwable => {
|
||||
e.printStackTrace()
|
||||
nbt.setInteger("state", Computer.State.Stopped.id)
|
||||
}
|
||||
}
|
||||
finally {
|
||||
// Clean up some after we're done and limit memory again.
|
||||
lua.gc(LuaState.GcAction.COLLECT, 0)
|
||||
lua.setTotalMemory(memory)
|
||||
}
|
||||
}
|
||||
|
||||
private def persist(index: Int): Array[Byte] = {
|
||||
lua.getGlobal("persist") // ... obj persist?
|
||||
@ -404,8 +402,6 @@ class Computer(val owner: IComputerEnvironment) extends IComputer with Runnable
|
||||
false
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------- //
|
||||
// Internals
|
||||
// ----------------------------------------------------------------------- //
|
||||
|
||||
private def init(): Boolean = {
|
||||
@ -523,11 +519,11 @@ class Computer(val owner: IComputerEnvironment) extends IComputer with Runnable
|
||||
}
|
||||
|
||||
lua.pushJavaFunction(ScalaFunction(lua =>
|
||||
owner.network.sendToNode(owner, lua.checkInteger(1), lua.checkString(2), parseArguments(lua, 3): _*) match {
|
||||
owner.network.sendToAddress(owner, lua.checkInteger(1), lua.checkString(2), parseArguments(lua, 3): _*) match {
|
||||
case Some(Array(results@_*)) =>
|
||||
results.foreach(pushResult(lua, _))
|
||||
results.length
|
||||
case None => 0
|
||||
case _ => 0
|
||||
}))
|
||||
lua.setGlobal("sendToNode")
|
||||
|
||||
@ -612,61 +608,59 @@ class Computer(val owner: IComputerEnvironment) extends IComputer with Runnable
|
||||
signals.clear()
|
||||
timeStarted = 0
|
||||
future = None
|
||||
sleepUntil = Long.MaxValue
|
||||
|
||||
// Mark state change in owner, to send it to clients.
|
||||
owner.markAsChanged()
|
||||
})
|
||||
|
||||
private def execute(value: Computer.State.Value) {
|
||||
assert(future.isEmpty)
|
||||
sleepUntil = Long.MaxValue
|
||||
state = value
|
||||
future = Some(Computer.Executor.pool.submit(this))
|
||||
}
|
||||
|
||||
// This is a really high level lock that we only use for saving and loading.
|
||||
override def run(): Unit = this.synchronized {
|
||||
// See if the game appears to be paused, in which case we also pause.
|
||||
if (System.currentTimeMillis - lastUpdate > 200)
|
||||
stateMonitor.synchronized {
|
||||
state =
|
||||
if (state == Computer.State.SynchronizedReturn) Computer.State.SynchronizedReturnPaused
|
||||
else Computer.State.Paused
|
||||
val callReturn = stateMonitor.synchronized {
|
||||
val oldState = state
|
||||
state = Computer.State.Running
|
||||
|
||||
// See if the game appears to be paused, in which case we also pause.
|
||||
if (System.currentTimeMillis - lastUpdate > 200) {
|
||||
state = state match {
|
||||
case Computer.State.SynchronizedReturn => Computer.State.SynchronizedReturnPaused
|
||||
case _ => Computer.State.Paused
|
||||
}
|
||||
future = None
|
||||
return
|
||||
}
|
||||
|
||||
val callReturn = stateMonitor.synchronized {
|
||||
if (state == Computer.State.Stopped) return
|
||||
val oldState = state
|
||||
state = Computer.State.Running
|
||||
future = None
|
||||
oldState
|
||||
} match {
|
||||
case Computer.State.SynchronizedReturn | Computer.State.SynchronizedReturnPaused => true
|
||||
case Computer.State.Stopped | Computer.State.Paused | Computer.State.Suspended | Computer.State.Sleeping => false
|
||||
case Computer.State.SynchronizedReturn => true
|
||||
case Computer.State.Yielded | Computer.State.Sleeping => false
|
||||
case s =>
|
||||
OpenComputers.log.warning("Running computer from invalid state " + s.toString + "!")
|
||||
stateMonitor.synchronized {
|
||||
state = s
|
||||
future = None
|
||||
}
|
||||
OpenComputers.log.warning("Running computer from invalid state " + s.toString + ". This is a bug!")
|
||||
close()
|
||||
return
|
||||
}
|
||||
|
||||
// The kernel thread will always be at stack index one.
|
||||
assert(lua.isThread(1))
|
||||
|
||||
try {
|
||||
// This is synchronized so that we don't run a Lua state while saving or
|
||||
// loading the computer to or from an NBTTagCompound or other stuff
|
||||
// corrupting our Lua state.
|
||||
|
||||
// The kernel thread will always be at stack index one.
|
||||
assert(lua.isThread(1))
|
||||
|
||||
// Resume the Lua state and remember the number of results we get.
|
||||
val results = if (callReturn) {
|
||||
// If we were doing a driver call, continue where we left off.
|
||||
// If we were doing a synchronized call, continue where we left off.
|
||||
assert(lua.getTop == 2)
|
||||
assert(lua.isTable(2))
|
||||
lua.resume(1, 1)
|
||||
}
|
||||
else signals.poll() match {
|
||||
// No signal, just run any non-sleeping processes.
|
||||
case null => lua.resume(1, 0)
|
||||
|
||||
// Got a signal, inject it and call any handlers (if any).
|
||||
case signal => {
|
||||
else Option(signals.poll()) match {
|
||||
case None => lua.resume(1, 0)
|
||||
case Some(signal) => {
|
||||
lua.pushString(signal.name)
|
||||
signal.args.foreach {
|
||||
case Unit => lua.pushNil()
|
||||
@ -678,70 +672,82 @@ class Computer(val owner: IComputerEnvironment) extends IComputer with Runnable
|
||||
}
|
||||
}
|
||||
|
||||
// State has inevitably changed, mark as changed to save again.
|
||||
owner.markAsChanged()
|
||||
|
||||
// Only queue for next execution step if the kernel is still alive.
|
||||
if (lua.status(1) == LuaState.YIELD) {
|
||||
// Lua state yielded normally, see what we have.
|
||||
stateMonitor.synchronized {
|
||||
if (state == Computer.State.Stopping) {
|
||||
// Someone called stop() in the meantime.
|
||||
close()
|
||||
}
|
||||
else if (results == 1 && lua.isNumber(2)) {
|
||||
// We got a number. This tells us how long we should wait before
|
||||
// resuming the state again.
|
||||
val sleep = (lua.toNumber(2) * 1000).toLong
|
||||
lua.pop(results)
|
||||
if (signals.isEmpty) {
|
||||
state = Computer.State.Sleeping
|
||||
assert(!future.isDefined)
|
||||
future = Some(Computer.Executor.pool.schedule(this, sleep, TimeUnit.MILLISECONDS))
|
||||
}
|
||||
else {
|
||||
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 = Computer.State.SynchronizedCall
|
||||
assert(!future.isDefined)
|
||||
}
|
||||
else {
|
||||
// Something else, just pop the results and try again.
|
||||
lua.pop(results)
|
||||
state = Computer.State.Suspended
|
||||
assert(!future.isDefined)
|
||||
if (!signals.isEmpty) future = Some(Computer.Executor.pool.submit(this))
|
||||
// Check if the kernel is still alive.
|
||||
stateMonitor.synchronized(if (lua.status(1) == LuaState.YIELD) {
|
||||
// Intermediate state in some cases. Satisfies the assert in execute().
|
||||
future = None
|
||||
// Someone called stop() in the meantime.
|
||||
if (state == Computer.State.Stopping)
|
||||
close()
|
||||
// If we have a single number that's how long we may wait before
|
||||
// resuming the state again.
|
||||
else if (results == 1 && lua.isNumber(2)) {
|
||||
val sleep = (lua.toNumber(2) * 1000).toLong
|
||||
lua.pop(results)
|
||||
// But only sleep if we don't have more signals to process.
|
||||
if (signals.isEmpty) {
|
||||
state = Computer.State.Sleeping
|
||||
sleepUntil = System.currentTimeMillis + sleep
|
||||
}
|
||||
else execute(Computer.State.Yielded)
|
||||
}
|
||||
// If we get one function it must be a wrapper for a synchronized call.
|
||||
// The protocol is that a closure is pushed that is then called from
|
||||
// the main server thread, and returns a table, which is in turn passed
|
||||
// to the originating coroutine.yield().
|
||||
else if (results == 1 && lua.isFunction(2))
|
||||
state = Computer.State.SynchronizedCall
|
||||
// Check if we are shutting down, and if so if we're rebooting. This is
|
||||
// signalled by boolean values, where `false` means shut down, `true`
|
||||
// means reboot (i.e shutdown then start again).
|
||||
else if (results == 1 && lua.isBoolean(2)) {
|
||||
val reboot = lua.toBoolean(2)
|
||||
close()
|
||||
if (reboot)
|
||||
start()
|
||||
}
|
||||
else {
|
||||
// Something else, just pop the results and try again.
|
||||
lua.pop(results)
|
||||
if (signals.isEmpty)
|
||||
state = Computer.State.Suspended
|
||||
else
|
||||
execute(Computer.State.Yielded)
|
||||
}
|
||||
|
||||
// Avoid getting to the closing part after the exception handling.
|
||||
return
|
||||
}
|
||||
// Error handling.
|
||||
else if (lua.isBoolean(2) && !lua.toBoolean(2)) {
|
||||
// TODO Print something to an in-game screen.
|
||||
OpenComputers.log.warning(lua.toString(3))
|
||||
}
|
||||
// The kernel thread returned. If it threw we'd we in the catch below.
|
||||
else {
|
||||
assert(lua.isThread(1))
|
||||
// We're expecting the result of a pcall, if anything, so boolean + (result | string).
|
||||
if (!lua.isBoolean(2) || !(lua.isString(3) || lua.isNil(3))) {
|
||||
OpenComputers.log.warning("Kernel returned unexpected results.")
|
||||
}
|
||||
// The pcall *should* never return normally... but check for it nonetheless.
|
||||
if (lua.toBoolean(2)) {
|
||||
OpenComputers.log.warning("Kernel stopped unexpectedly.")
|
||||
}
|
||||
else {
|
||||
OpenComputers.log.warning("Computer crashed.\n" + lua.toString(3)) // TODO remove this when we have a component that can display crash messages
|
||||
// TODO get this to the world as a computer.crashed message. problem: synchronizing it.
|
||||
//owner.network.sendToAll(owner, "computer.crashed", lua.toString(3))
|
||||
}
|
||||
close()
|
||||
})
|
||||
}
|
||||
catch {
|
||||
case er: LuaMemoryAllocationException => {
|
||||
// This is pretty likely to happen for non-upgraded computers.
|
||||
// TODO Print something to an in-game screen, a la kernel panic.
|
||||
OpenComputers.log.warning("Out of memory!")
|
||||
case e: LuaRuntimeException =>
|
||||
OpenComputers.log.warning("Kernel crashed. This is a bug!\n" + e.toString + "\tat " + e.getLuaStackTrace.mkString("\n\tat "))
|
||||
close()
|
||||
case e: LuaMemoryAllocationException => {
|
||||
OpenComputers.log.warning("Out of memory!") // TODO remove this when we have a component that can display crash messages
|
||||
// TODO get this to the world as a computer.crashed message. problem: synchronizing it.
|
||||
//owner.network.sendToAll(owner, "computer.crashed", "not enough memory")
|
||||
close()
|
||||
}
|
||||
// Top-level catch-anything, because otherwise those exceptions get
|
||||
// gobbled up by the executor unless we call the future's get().
|
||||
case t: Throwable =>
|
||||
OpenComputers.log.log(Level.WARNING, "Faulty kernel implementation, it should never throw.", t)
|
||||
}
|
||||
|
||||
// If we come here there was an error or we stopped, kill off the state.
|
||||
close()
|
||||
// State has inevitably changed, mark as changed to save again.
|
||||
owner.markAsChanged()
|
||||
}
|
||||
}
|
||||
|
||||
@ -765,9 +771,12 @@ object Computer {
|
||||
/** The computer is not running right now and there is no Lua state. */
|
||||
val Stopped = Value("Stopped")
|
||||
|
||||
/** The computer is running but yielded for a moment. */
|
||||
/** The computer is running but yielded and there were no more signals to process. */
|
||||
val Suspended = Value("Suspended")
|
||||
|
||||
/** The computer is running but yielded but will resume as soon as possible. */
|
||||
val Yielded = Value("Yielded")
|
||||
|
||||
/** The computer is running but yielding for a longer amount of time. */
|
||||
val Sleeping = Value("Sleeping")
|
||||
|
||||
@ -808,6 +817,11 @@ object Computer {
|
||||
thread.setDaemon(true)
|
||||
if (thread.getPriority != Thread.MIN_PRIORITY)
|
||||
thread.setPriority(Thread.MIN_PRIORITY)
|
||||
thread.setUncaughtExceptionHandler(new UncaughtExceptionHandler {
|
||||
def uncaughtException(t: Thread, e: Throwable) {
|
||||
OpenComputers.log.log(Level.WARNING, "Unhandled exception in worker thread.", e)
|
||||
}
|
||||
})
|
||||
thread
|
||||
}
|
||||
})
|
||||
|
@ -100,7 +100,7 @@ private[oc] object Drivers {
|
||||
case Some(code) =>
|
||||
val name = driver.getClass.getName
|
||||
try {
|
||||
computer.lua.load(code, name, "t") // ... func
|
||||
computer.lua.load(code, "=" + name, "t") // ... func
|
||||
code.close()
|
||||
computer.lua.call(0, 0) // ...
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
package li.cil.oc.server.computer
|
||||
|
||||
import li.cil.oc.api.INetworkNode
|
||||
import li.cil.oc.api.{Visibility, INetworkNode}
|
||||
import net.minecraft.world.World
|
||||
|
||||
/**
|
||||
@ -10,6 +10,8 @@ import net.minecraft.world.World
|
||||
trait IComputerEnvironment extends INetworkNode {
|
||||
override def name = "computer"
|
||||
|
||||
override def visibility = Visibility.Network
|
||||
|
||||
def world: World
|
||||
|
||||
/**
|
||||
|
@ -2,9 +2,7 @@ package li.cil.oc.server.computer
|
||||
|
||||
import java.util.logging.Level
|
||||
import li.cil.oc.OpenComputers
|
||||
import li.cil.oc.api.INetwork
|
||||
import li.cil.oc.api.INetworkMessage
|
||||
import li.cil.oc.api.INetworkNode
|
||||
import li.cil.oc.api.{Visibility, INetwork, INetworkMessage, INetworkNode}
|
||||
import net.minecraft.block.Block
|
||||
import net.minecraft.tileentity.TileEntity
|
||||
import net.minecraft.world.{World, IBlockAccess}
|
||||
@ -34,7 +32,7 @@ class Network private(private val nodeMap: mutable.Map[Int, ArrayBuffer[Network.
|
||||
node.address = 1
|
||||
node.address -> ArrayBuffer(new Network.Node(node))
|
||||
}))
|
||||
Network.send(new Network.ConnectMessage(node), List(node))
|
||||
send(new Network.ConnectMessage(node), List(node))
|
||||
}
|
||||
|
||||
nodes.foreach(_.network = this)
|
||||
@ -69,13 +67,22 @@ class Network private(private val nodeMap: mutable.Map[Int, ArrayBuffer[Network.
|
||||
private def add(oldNode: Network.Node, addedNode: INetworkNode) = {
|
||||
// Check if the other node is new or if we have to merge networks.
|
||||
val (newNode, sendQueue) = if (addedNode.network == null) {
|
||||
val sendQueue = mutable.Buffer.empty[(Network.Message, Iterable[INetworkNode])]
|
||||
sendQueue += ((new Network.ConnectMessage(addedNode), List(addedNode) ++ nodes))
|
||||
nodes.foreach(node => sendQueue += ((new Network.ConnectMessage(node), List(addedNode))))
|
||||
val newNode = new Network.Node(addedNode)
|
||||
if (nodeMap.contains(addedNode.address) || addedNode.address < 1)
|
||||
addedNode.address = findId()
|
||||
nodeMap.getOrElseUpdate(addedNode.address, new ArrayBuffer[Network.Node]) += newNode
|
||||
// Store everything with an invalid address in slot zero.
|
||||
val address = addedNode.address match {
|
||||
case a if a > 0 => a
|
||||
case _ => 0
|
||||
}
|
||||
// Create the message queue. The address check is purely for performance,
|
||||
// since we can skip all that if the node is non-valid.
|
||||
val sendQueue = mutable.Buffer.empty[(Network.Message, Iterable[INetworkNode])]
|
||||
if (address > 0 && addedNode.visibility != Visibility.None) {
|
||||
sendQueue += ((new Network.ConnectMessage(addedNode), List(addedNode) ++ nodes))
|
||||
nodes.foreach(node => sendQueue += ((new Network.ConnectMessage(node), List(addedNode))))
|
||||
}
|
||||
nodeMap.getOrElseUpdate(address, new ArrayBuffer[Network.Node]) += newNode
|
||||
addedNode.network = this
|
||||
(newNode, sendQueue)
|
||||
}
|
||||
@ -88,7 +95,10 @@ class Network private(private val nodeMap: mutable.Map[Int, ArrayBuffer[Network.
|
||||
otherNodes.foreach(node => sendQueue += ((new Network.ConnectMessage(node), thisNodes)))
|
||||
thisNodes.foreach(node => sendQueue += ((new Network.ConnectMessage(node), otherNodes)))
|
||||
|
||||
// Change addresses for conflicting nodes in other network.
|
||||
// Change addresses for conflicting nodes in other network. We can queue
|
||||
// these messages because we're storing references to the nodes, so they
|
||||
// will send the change notification to the right node even if that node
|
||||
// also changes its address.
|
||||
val reserved = mutable.Set(otherNetwork.nodeMap.keySet.toSeq: _*)
|
||||
otherNodes.filter(node => nodeMap.contains(node.address)).foreach(node => {
|
||||
val oldAddress = node.address
|
||||
@ -100,7 +110,7 @@ class Network private(private val nodeMap: mutable.Map[Int, ArrayBuffer[Network.
|
||||
}
|
||||
})
|
||||
|
||||
// Add nodes from other network into this network.
|
||||
// Add nodes from other network into this network, including invalid nodes.
|
||||
otherNetwork.nodeMap.values.flatten.foreach(node => {
|
||||
nodeMap.getOrElseUpdate(node.data.address, new ArrayBuffer[Network.Node]) += node
|
||||
node.data.network = this
|
||||
@ -114,7 +124,7 @@ class Network private(private val nodeMap: mutable.Map[Int, ArrayBuffer[Network.
|
||||
Network.Edge(oldNode, newNode)
|
||||
|
||||
// Send all generated messages.
|
||||
for ((message, nodes) <- sendQueue) Network.send(message, nodes)
|
||||
for ((message, nodes) <- sendQueue) send(message, nodes)
|
||||
|
||||
true
|
||||
}
|
||||
@ -148,10 +158,10 @@ class Network private(private val nodeMap: mutable.Map[Int, ArrayBuffer[Network.
|
||||
// of which we'll re-use for this network. For all additional ones we
|
||||
// create new network instances.
|
||||
handleSplit(entry.remove(), nodes => {
|
||||
nodes.foreach(n => Network.send(new Network.DisconnectMessage(n), List(node)))
|
||||
Network.send(new Network.DisconnectMessage(node), nodes)
|
||||
nodes.foreach(n => send(new Network.DisconnectMessage(n), List(node)))
|
||||
send(new Network.DisconnectMessage(node), nodes)
|
||||
})
|
||||
Network.send(new Network.DisconnectMessage(node), List(node))
|
||||
send(new Network.DisconnectMessage(node), List(node))
|
||||
true
|
||||
}
|
||||
}
|
||||
@ -181,19 +191,24 @@ class Network private(private val nodeMap: mutable.Map[Int, ArrayBuffer[Network.
|
||||
val nodesA = subNodes(indexA)
|
||||
for (indexB <- (indexA + 1) until subNodes.length) {
|
||||
val nodesB = subNodes(indexB)
|
||||
nodesA.foreach(nodeA => Network.send(new Network.DisconnectMessage(nodeA), nodesB))
|
||||
nodesB.foreach(nodeB => Network.send(new Network.DisconnectMessage(nodeB), nodesA))
|
||||
nodesA.foreach(nodeA => send(new Network.DisconnectMessage(nodeA), nodesB))
|
||||
nodesB.foreach(nodeB => send(new Network.DisconnectMessage(nodeB), nodesA))
|
||||
}
|
||||
messageCallback(nodesA)
|
||||
}
|
||||
}
|
||||
|
||||
def node(address: Int) = nodeMap.get(address) match {
|
||||
case None => None
|
||||
case Some(list) => Some(list.last.data)
|
||||
case Some(list) if address > 0 => list.map(_.data).filter(_.visibility != Visibility.None).lastOption
|
||||
case _ => None
|
||||
}
|
||||
|
||||
def nodes = nodeMap.values.flatten.map(_.data)
|
||||
def nodes(reference: INetworkNode) = {
|
||||
val referenceNeighbors = neighbors(reference).toSet
|
||||
nodes.filter(node => node.visibility == Visibility.Network || referenceNeighbors.contains(node))
|
||||
}
|
||||
|
||||
def nodes = nodeMap.filter(_._1 > 0).values.flatten.map(_.data).filter(_.visibility != Visibility.None)
|
||||
|
||||
def neighbors(node: INetworkNode) = nodeMap.get(node.address) match {
|
||||
case None => throw new IllegalArgumentException("Node must be in this network.")
|
||||
@ -203,14 +218,74 @@ class Network private(private val nodeMap: mutable.Map[Int, ArrayBuffer[Network.
|
||||
}
|
||||
}
|
||||
|
||||
def sendToNode(source: INetworkNode, target: Int, name: String, data: Any*) =
|
||||
def sendToAddress(source: INetworkNode, target: Int, name: String, data: Any*) =
|
||||
nodeMap.get(target) match {
|
||||
case None => None
|
||||
case Some(list) => Network.send(new Network.Message(source, name, Array(data: _*)), list.map(_.data))
|
||||
case Some(list) => send(new Network.Message(source, name, Array(data: _*)), list.map(_.data))
|
||||
}
|
||||
|
||||
def sendToNeighbors(source: INetworkNode, name: String, data: Any*) =
|
||||
send(new Network.Message(source, name, Array(data: _*)), neighbors(source))
|
||||
|
||||
def sendToAll(source: INetworkNode, name: String, data: Any*) =
|
||||
Network.send(new Network.Message(source, name, Array(data: _*)), nodes)
|
||||
send(new Network.Message(source, name, Array(data: _*)), nodes)
|
||||
|
||||
private def send(message: Network.Message, targets: Iterable[INetworkNode]) =
|
||||
if (message.source.address > 0 && message.source.visibility != Visibility.None) {
|
||||
def debug(target: INetworkNode) = {} // println("receive(" + message.name + "(" + message.data.mkString(", ") + "): " + message.source.address + ":" + message.source.name + " -> " + target.address + ":" + target.name + ")")
|
||||
message match {
|
||||
case _@(Network.ConnectMessage(_) | Network.ReconnectMessage(_, _)) =>
|
||||
// Cannot be canceled but respects visibility.
|
||||
message.source.visibility match {
|
||||
case Visibility.Neighbors =>
|
||||
val neighborSet = neighbors(message.source).toSet
|
||||
val iterator = targets.filter(target => target == message.source || neighborSet.contains(target)).iterator
|
||||
while (iterator.hasNext) try {
|
||||
val target = iterator.next()
|
||||
debug(target)
|
||||
target.receive(message)
|
||||
} catch {
|
||||
case e: Throwable => OpenComputers.log.log(Level.WARNING, "Error in message handler", e)
|
||||
}
|
||||
case Visibility.Network =>
|
||||
val iterator = targets.filter(_.address > 0).filter(_.visibility == Visibility.Network).iterator
|
||||
while (iterator.hasNext) try {
|
||||
val target = iterator.next()
|
||||
debug(target)
|
||||
target.receive(message)
|
||||
} catch {
|
||||
case e: Throwable => OpenComputers.log.log(Level.WARNING, "Error in message handler", e)
|
||||
}
|
||||
}
|
||||
None
|
||||
case _@Network.DisconnectMessage(_) =>
|
||||
// Cannot be canceled but ignores visibility (because it'd be a pain to implement this otherwise).
|
||||
val iterator = targets.filter(_.address > 0).iterator
|
||||
while (iterator.hasNext) try {
|
||||
val target = iterator.next()
|
||||
debug(target)
|
||||
target.receive(message)
|
||||
} catch {
|
||||
case e: Throwable => OpenComputers.log.log(Level.WARNING, "Error in message handler", e)
|
||||
}
|
||||
None
|
||||
case _ =>
|
||||
// Can be canceled but ignores visibility.
|
||||
var result = None: Option[Array[Any]]
|
||||
val iterator = targets.filter(_.address > 0).iterator
|
||||
while (!message.isCanceled && iterator.hasNext) try {
|
||||
val target = iterator.next()
|
||||
debug(target)
|
||||
target.receive(message) match {
|
||||
case None => // Ignore.
|
||||
case r => result = r
|
||||
}
|
||||
} catch {
|
||||
case e: Throwable => OpenComputers.log.log(Level.WARNING, "Error in message handler", e)
|
||||
}
|
||||
result
|
||||
}
|
||||
} else None
|
||||
|
||||
private def findId(reserved: collection.Set[Int] = collection.Set.empty[Int]) = Range(1, Int.MaxValue).find(
|
||||
address => !nodeMap.contains(address) && !reserved.contains(address)).get
|
||||
@ -322,26 +397,10 @@ object Network {
|
||||
def cancel() = isCanceled = true
|
||||
}
|
||||
|
||||
private class ConnectMessage(source: INetworkNode) extends Message(source, "network.connect")
|
||||
private case class ConnectMessage(override val source: INetworkNode) extends Message(source, "network.connect")
|
||||
|
||||
private class DisconnectMessage(source: INetworkNode) extends Message(source, "network.disconnect")
|
||||
private case class DisconnectMessage(override val source: INetworkNode) extends Message(source, "network.disconnect")
|
||||
|
||||
private class ReconnectMessage(source: INetworkNode, oldAddress: Int) extends Message(source, "network.reconnect", Array(oldAddress.asInstanceOf[Any]))
|
||||
private case class ReconnectMessage(override val source: INetworkNode, oldAddress: Int) extends Message(source, "network.reconnect", Array(oldAddress.asInstanceOf[Any]))
|
||||
|
||||
private def send(message: Network.Message, nodes: Iterable[INetworkNode]) = {
|
||||
//println("send(" + message.name + "(" + message.data.mkString(", ") + "): " + message.source.address + ":" + message.source.name + " -> [" + nodes.map(node => node.address + ":" + node.name).mkString(", ") + "])")
|
||||
val iterator = nodes.iterator
|
||||
var result = None: Option[Array[Any]]
|
||||
while (!message.isCanceled && iterator.hasNext) {
|
||||
try {
|
||||
iterator.next().receive(message) match {
|
||||
case None => // Ignore.
|
||||
case r => result = r
|
||||
}
|
||||
} catch {
|
||||
case e: Throwable => OpenComputers.log.log(Level.WARNING, "Error in message handler", e)
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user