diff --git a/assets/opencomputers/lua/drivers/gpu.lua b/assets/opencomputers/lua/drivers/gpu.lua
index 5215543d7..8913e568c 100644
--- a/assets/opencomputers/lua/drivers/gpu.lua
+++ b/assets/opencomputers/lua/drivers/gpu.lua
@@ -1,4 +1,3 @@
---[[ API for graphics cards. ]]
driver.gpu = {}
function driver.gpu.setResolution(gpu, screen, w, h)
@@ -25,25 +24,25 @@ function driver.gpu.copy(gpu, screen, col, row, w, h, tx, ty)
sendToNode(gpu, "gpu.copy", screen, col, row, w, h, tx, ty)
end
-function driver.gpu.bind(gpuId, screenId)
+function driver.gpu.bind(gpu, screen)
return {
setResolution = function(w, h)
- driver.gpu.setResolution(component.address(gpuId), component.address(screenId), w, h)
+ driver.gpu.setResolution(gpu, screen, w, h)
end,
getResolution = function()
- return driver.gpu.getResolution(component.address(gpuId), component.address(screenId))
+ return driver.gpu.getResolution(gpu, screen)
end,
getResolutions = function()
- return driver.gpu.getResolutions(component.address(gpuId), component.address(screenId))
+ return driver.gpu.getResolutions(gpu, screen)
end,
set = function(col, row, value)
- driver.gpu.set(component.address(gpuId), component.address(screenId), col, row, value)
+ driver.gpu.set(gpu, screen, col, row, value)
end,
fill = function(col, ro, w, h, value)
- driver.gpu.fill(component.address(gpuId), component.address(screenId), col, ro, w, h, value)
+ driver.gpu.fill(gpu, screen, col, ro, w, h, value)
end,
copy = function(col, row, w, h, tx, ty)
- driver.gpu.copy(component.address(gpuId), component.address(screenId), col, row, w, h, tx, ty)
+ driver.gpu.copy(gpu, screen, col, row, w, h, tx, ty)
end
}
end
\ No newline at end of file
diff --git a/assets/opencomputers/lua/drivers/keyboard.lua b/assets/opencomputers/lua/drivers/keyboard.lua
index bf9dfda81..c600882ad 100644
--- a/assets/opencomputers/lua/drivers/keyboard.lua
+++ b/assets/opencomputers/lua/drivers/keyboard.lua
@@ -1,4 +1,3 @@
---[[ API for keyboards. ]]
driver.keyboard = {}
driver.keyboard.keys = {
diff --git a/assets/opencomputers/lua/drivers/redstone.lua b/assets/opencomputers/lua/drivers/redstone.lua
index 25f458282..799ec880c 100644
--- a/assets/opencomputers/lua/drivers/redstone.lua
+++ b/assets/opencomputers/lua/drivers/redstone.lua
@@ -1,6 +1,5 @@
---[[ API for Redstone Cards. ]]
-
driver.redstone = {}
+driver.rs = driver.redstone
driver.redstone.sides = {"top", "bottom", "left", "right", "front", "back"}
diff --git a/assets/opencomputers/lua/init.lua b/assets/opencomputers/lua/init.lua
index e33b1ef90..7a62a4278 100644
--- a/assets/opencomputers/lua/init.lua
+++ b/assets/opencomputers/lua/init.lua
@@ -37,7 +37,7 @@ local weakListeners = {}
local function listenersFor(name, weak)
checkArg(1, name, "string")
if weak then
- weakListeners[name] = weakListeners[name] or setmetatable({}, {__mode = "k"})
+ weakListeners[name] = weakListeners[name] or setmetatable({}, {__mode = "v"})
return weakListeners[name]
else
listeners[name] = listeners[name] or {}
@@ -52,13 +52,21 @@ local timers = {}
--[[ Register a new event listener for the specified event. ]]
function event.listen(name, callback, weak)
checkArg(2, callback, "function")
- listenersFor(name, weak)[callback] = true
+ table.insert(listenersFor(name, weak), callback)
end
--[[ Remove an event listener. ]]
function event.ignore(name, callback)
- listenersFor(name, false)[callback] = nil
- listenersFor(name, true)[callback] = nil
+ local function remove(list)
+ for k, v in ipairs(list) do
+ if v == callback then
+ table.remove(k)
+ return
+ end
+ end
+ end
+ remove(listenersFor(name, false))
+ remove(listenersFor(name, true))
end
--[[ Dispatch an event with the specified parameter. ]]
@@ -67,13 +75,13 @@ function event.fire(name, ...)
-- 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
+ for _, callback in ipairs(listenersFor(name, false)) do
local result, message = xpcall(callback, event.error, name, ...)
if not result and message then
error(message, 0)
end
end
- for callback, _ in pairs(listenersFor(name, true)) do
+ for _, callback in ipairs(listenersFor(name, true)) do
local result, message = xpcall(callback, event.error, name, ...)
if not result and message then
error(message, 0)
@@ -133,100 +141,64 @@ end
-------------------------------------------------------------------------------
---[[ Keep track of connected components across address changes. ]]
+--[[ Keep track of connected components. ]]
local components = {}
component = {}
-function component.address(id)
- local component = components[id]
+function component.type(address)
+ local component = components[address]
if component then
- return component.address
+ return component
end
end
-function component.type(id)
- local component = components[id]
- if component then
- return component.name
- end
-end
-
-function component.id(address)
- for id, component in pairs(components) do
- if component.address == address then
- return id
- end
- end
-end
-
-function component.ids()
- local id = nil
+function component.list()
+ local address = nil
return function()
- id = next(components, id)
- return id
+ address = next(components, address)
+ return address
end
end
event.listen("component_added", function(_, address)
- local id = #components + 1
- components[id] = {address = address, name = driver.componentType(address)}
- event.fire("component_installed", id)
+ components[address] = driver.componentType(address)
end)
event.listen("component_removed", function(_, address)
- local id = component.id(address)
- 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)
- 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
+ components[address] = nil
end)
-------------------------------------------------------------------------------
--[[ Setup terminal API. ]]
-local gpuId, screenId = 0, 0
+local gpuAddress, screenAddress = false, false
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 gpuId < 1 then
- term.gpuId(id)
- elseif type == "screen" and screenId < 1 then
- term.screenId(id)
+event.listen("component_added", function(_, address)
+ local type = component.type(address)
+ if type == "gpu" and not gpuAddress then
+ term.gpu(address)
+ elseif type == "screen" and not screenAddress then
+ term.screen(address)
end
end)
-event.listen("component_uninstalled", function(_, id)
- if gpuId == id then
- term.gpuId(0)
- for id in component.ids() do
- if component.type(id) == "gpu" then
- term.gpuId(id)
+event.listen("component_removed", function(_, address)
+ if gpuAddress == address then
+ term.gpu(false)
+ for address in component.list() do
+ if component.type(address) == "gpu" then
+ term.gpu(address)
return
end
end
- elseif screenId == id then
- term.screenId(0)
- for id in component.ids() do
- if component.type(id) == "screen" then
- term.screenId(id)
+ elseif screenAddress == address then
+ term.screen(false)
+ for address in component.list() do
+ if component.type(address) == "screen" then
+ term.screen(address)
return
end
end
@@ -234,17 +206,16 @@ event.listen("component_uninstalled", function(_, id)
end)
event.listen("screen_resized", function(_, address, w, h)
- local id = component.id(address)
- if id == screenId then
+ if address == screenAddress then
screenWidth = w
screenHeight = h
end
end)
local function bindIfPossible()
- if gpuId > 0 and screenId > 0 then
+ if gpuAddress and screenAddress then
if not boundGpu then
- boundGpu = driver.gpu.bind(gpuId, screenId)
+ boundGpu = driver.gpu.bind(gpuAddress, screenAddress)
screenWidth, screenHeight = boundGpu.getResolution()
event.fire("term_available")
end
@@ -257,30 +228,26 @@ end
term = {}
-function term.gpu()
- return boundGpu
-end
-
function term.screenSize()
return screenWidth, screenHeight
end
-function term.gpuId(id)
- if id then
- checkArg(1, id, "number")
- gpuId = id
+function term.gpu(address)
+ if address ~= nil then
+ checkArg(1, address, "string", "boolean")
+ gpuAddress = address
bindIfPossible()
end
- return gpuId
+ return gpuAddress
end
-function term.screenId(id)
- if id then
- checkArg(1, id, "number")
- screenId = id
+function term.screen(address)
+ if address ~= nil then
+ checkArg(1, address, "string", "boolean")
+ screenAddress = address
bindIfPossible()
end
- return screenId
+ return screenAddress
end
function term.getCursor()
@@ -359,23 +326,23 @@ end
-------------------------------------------------------------------------------
--[[ Primitive command line. ]]
-local keyboardId = 0
+local keyboardAddress = false
local lastCommand, command = "", ""
local isRunning = false
-event.listen("component_installed", function(_, id)
- local type = component.type(id)
- if type == "keyboard" and keyboardId < 1 then
- keyboardId = id
+event.listen("component_added", function(_, address)
+ local type = component.type(address)
+ if type == "keyboard" and not keyboardAddress then
+ term.keyboardAddress(address)
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
+event.listen("component_uninstalled", function(_, address)
+ if keyboardAddress == address then
+ term.keyboardAddress(false)
+ for address in component.list() do
+ if component.type(address) == "keyboard" then
+ term.keyboardAddress(address)
return
end
end
@@ -383,17 +350,17 @@ event.listen("component_uninstalled", function(_, id)
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
+function term.keyboardAddress(address)
+ if address ~= nil then
+ checkArg(1, address, "string", "boolean")
+ keyboardAddress = address
end
- return keyboardId
+ return keyboardAddress
end
local function onKeyDown(_, address, char, code)
if isRunning then return end -- ignore events while running a command
- if component.id(address) ~= keyboardId then return end
+ if address ~= keyboardAddress then return end
if not boundGpu then return end
local x, y = term.getCursor()
local keys = driver.keyboard.keys
@@ -438,7 +405,7 @@ end
local function onClipboard(_, address, value)
if isRunning then return end -- ignore events while running a command
- if component.id(address) ~= keyboardId then return end
+ if address ~= keyboardAddress then return end
value = value:match("([^\r\n]+)")
if value and value:len() > 0 then
command = command .. value
@@ -466,13 +433,12 @@ end)
local blinkState = false
while true do
coroutine.sleep(0.5)
- local gpu = term.gpu()
- if gpu then
+ if boundGpu then
local x, y = term.getCursor()
if blinkState then
- term.gpu().set(x, y, string.char(0x2588)) -- Solid block.
+ boundGpu.set(x, y, string.char(0x2588)) -- Solid block.
else
- term.gpu().set(x, y, " ")
+ boundGpu.set(x, y, " ")
end
end
blinkState = not blinkState
diff --git a/li/cil/oc/api/Network.scala b/li/cil/oc/api/Network.scala
index 6765ca2b5..efad63726 100644
--- a/li/cil/oc/api/Network.scala
+++ b/li/cil/oc/api/Network.scala
@@ -18,24 +18,23 @@ import net.minecraft.world.IBlockAccess
* then merge it with the other(s). If no networks exist, it should create a new
* one. All this logic is provided by `Network.joinOrCreateNetwork`.
*
- * Note that for network nodes implemented in TileEntities adding and
+ * Note that for network nodes implemented in `TileEntities` adding and
* removal is automatically provided on chunk load and unload. When a block is
* placed or broken you will have to implement this logic yourself (i.e. call
- * Network.joinOrCreateNetwork in onBlockAdded and
- * Network.remove in breakBlock.
+ * `Network.joinOrCreateNetwork` in `onBlockAdded` and `Network.remove` in
+ * `breakBlock`.
*
* All other kinds of nodes have to be managed manually. See `Node`.
*
* There are a couple of system messages to be aware of. These are all sent by
* the network manager itself:
*
- * - network.connect is generated when a node is added to the
- * network, with the added node as the sender.
- * - network.disconnect is generated when a node is removed from the
- * network, with the removed node as the sender.
- * - network.reconnect is generated when a node's address changes,
- * usually due to a network merge, with the node whose address changed as the
- * sender and the old address as the only parameter.
+ * - `network.connect` is generated when a node is added to the network,
+ * with the added node as the sender. This will also be sent to the nodes of
+ * the other network, when a network merges with another one (both ways).
+ * - `network.disconnect` is generated when a node is removed from the
+ * network, with the removed node as the sender. This will also be sent to the
+ * nodes of the other network(s), when a network is split (all pairs).
*
*
* IMPORTANT: do not implement this interface yourself and create
@@ -51,7 +50,7 @@ trait Network {
* This is used by nodes to join an existing network. At least one of the two
* nodes must already be in the network. If one of the nodes is not yet in the
* network, it will be added to the network. If both nodes are already in the
- * network only the connection between the two nodes is stored. If one of the
+ * network only the connection between the two nodes is added. If one of the
* nodes is not in this network but in another network, the networks will be
* merged.
*
@@ -66,26 +65,10 @@ trait Network {
*/
def connect(nodeA: Node, nodeB: Node): Boolean
- /**
- * Changes the address of a node.
- *
- * If another node with the specified address already exists in the network
- * it will be forced to change its address to an arbitrarily assigned, not
- * taken one.
- *
- * This is mainly used to restore a nodes address after it was loaded from
- * an old state (chunk load).
- *
- * @param node the node to change the address of.
- * @param address the new address of the node.
- * @return whether the node's address changed.
- */
- def reconnect(node: Node, address: Int): Boolean
-
/**
* Removes a node connection in the network.
*
- * Both nodes must be part of the same network.
+ * Both nodes must be part of this network.
*
* This can be useful for cutting connections that depend on some condition
* that does not involve the nodes' actual existence in the network, such as
@@ -102,36 +85,30 @@ trait Network {
/**
* Removes a node from the network.
*
- * This should be called by nodes when they are destroyed (e.g. onBreakBlock)
- * or unloaded. If removing the node leads to two graphs (it was the a bridge
- * node) the network will be split up.
+ * This should be called by nodes when they are destroyed (e.g. `breakBlock`)
+ * or unloaded. Removing the node can lead to one or more new networks if it
+ * was the a bridge node, i.e. the only node connecting the resulting
+ * networks.
*
* @param node the node to remove from the network.
- * @return whether the node was removed.
+ * @return true if the node was removed; false if it wasn't in the network.
*/
def remove(node: Node): Boolean
// ----------------------------------------------------------------------- //
/**
- * Get the valid network node with the specified address.
- *
- * This does not include nodes with an address less or equal to zero or with
- * a visibility of `Visibility.None`.
- *
- * If there are multiple nodes with the same address this will return the
- * node that most recently joined the network.
+ * Get the network node with the specified address.
*
* @param address the address of the node to get.
* @return the node with that address.
*/
- def node(address: Int): Option[Node]
+ def node(address: String): Option[Node]
/**
- * The list of all valid nodes in this network.
+ * The list of all nodes in this network.
*
- * This does not include nodes with an address less or equal to zero or with
- * a visibility of `Visibility.None`.
+ * This does *not* include nodes with a visibility of `Visibility.None`.
*
* @return the list of nodes in this network.
*/
@@ -140,12 +117,15 @@ trait Network {
/**
* The list of nodes in the network visible to the specified node.
*
- * The same base filters as for `nodes` apply, with additional visibility
- * checks applied, based on the specified node as a point of reference.
+ * This does *not* include nodes with a visibility of `Visibility.None` or
+ * a visibility of `Visibility.Neighbors` when there is no direct connection
+ * between that node and the reference node. This will always also contain
+ * the reference node itself, unless the reference node's visibility is
+ * `Visibility.None`.
*
- * 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 can be useful when performing a delayed initialization of a node.
+ * For example, computers will use this when starting up to generate
+ * `component_added` signals for all visible nodes in the network.
*
* @param reference the node to get the visible other nodes for.
* @return the nodes visible to the specified node.
@@ -153,10 +133,9 @@ trait Network {
def nodes(reference: Node): Iterable[Node]
/**
- * The list of valid nodes the specified node is directly connected to.
+ * The list of nodes the specified node is directly connected to.
*
- * This does not include nodes with an address less or equal to zero or with
- * a visibility of `Visibility.None`.
+ * This does *not* include nodes with a visibility of `Visibility.None`.
*
* This can be used to verify arguments for components that should only work
* for other components that are directly connected to them, for example.
@@ -170,17 +149,19 @@ trait Network {
// ----------------------------------------------------------------------- //
/**
- * Sends a message to a specific address, which may mean multiple nodes.
+ * Sends a message to a specific address.
*
- * 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
+ * If the target node with that address has a visibility of `Visibility.None`
+ * the message will not be delivered to that node. If the target node with
+ * that address has a visibility of `Visibility.Neighbors` and the source
+ * node is not directly connected to the target the message will not be
* delivered to that node.
*
* 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
+ * 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".
+ * this purpose should be named `computer.signal`.
*
* 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
@@ -193,53 +174,52 @@ trait Network {
* @param name the name of the message.
* @param data the message to send.
* @return the result of the message being handled, if any.
+ * @throws IllegalArgumentException if the source node is not in this network.
*/
- def sendToAddress(source: Node, target: Int, name: String, data: Any*): Option[Array[Any]]
+ def sendToAddress(source: Node, target: String, name: String, data: Any*): Option[Array[Any]]
/**
- * Sends a message to all direct valid neighbors of the source node.
+ * Sends a message to all direct neighbors of the source node.
*
- * This does not include nodes with an address less or equal to zero or with
- * a visibility of `Visibility.None`.
+ * Targets are determined using `neighbors`.
*
* 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
+ * 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".
+ * 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
+ * @throws IllegalArgumentException if the source node is not in this network.
+ * @see `neighbors`
*/
def sendToNeighbors(source: Node, name: String, data: Any*)
/**
- * Sends a message to all valid nodes in the network.
+ * Sends a message to all nodes in the network visible to the source node.
*
- * This does not include nodes with an address less or equal to zero or with
- * a visibility of `Visibility.None`.
- *
- * 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.
+ * Targets are determined using `nodes(source)`.
*
* 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
+ * 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".
+ * this purpose should be named `computer.signal`.
*
* @param source the node that sends the message.
* @param data the message to send.
+ * @throws IllegalArgumentException if the source node is not in this network.
+ * @see `nodes`
*/
- def sendToAll(source: Node, name: String, data: Any*)
+ def sendToVisible(source: Node, name: String, data: Any*)
}
object Network extends NetworkAPI {
/**
- * Tries to add a tile entity network node at the specified coordinates to adjacent networks.
+ * Tries to add a tile entity network node at the specified coordinates to
+ * adjacent networks.
*
* @param world the world the tile entity lives in.
* @param x the X coordinate of the tile entity.
diff --git a/li/cil/oc/api/driver/Item.scala b/li/cil/oc/api/driver/Item.scala
index 600ffe5fd..862196ac8 100644
--- a/li/cil/oc/api/driver/Item.scala
+++ b/li/cil/oc/api/driver/Item.scala
@@ -75,6 +75,8 @@ trait Item extends Driver {
* @return the tag to use for saving and loading.
*/
def nbt(item: ItemStack) = {
+ if (!item.hasTagCompound)
+ item.setTagCompound(new NBTTagCompound())
val nbt = item.getTagCompound
if (!nbt.hasKey("oc.node")) {
nbt.setCompoundTag("oc.node", new NBTTagCompound())
diff --git a/li/cil/oc/api/network/Node.scala b/li/cil/oc/api/network/Node.scala
index a7fd7f9a3..628e92c2e 100644
--- a/li/cil/oc/api/network/Node.scala
+++ b/li/cil/oc/api/network/Node.scala
@@ -1,16 +1,14 @@
package li.cil.oc.api.network
import li.cil.oc.api.{Persistable, Network}
-import net.minecraft.nbt.NBTTagCompound
+import net.minecraft.nbt.{NBTTagString, NBTTagCompound}
/**
* A single node in a `INetwork`.
*
- * All nodes in a network should have a unique address; the network
- * will try to generate a unique address and assign it to new nodes. A node must
- * never ever change its address while in a network (because the lookup-table in
- * the network manager would not be notified of this change). If you must change
- * the address, use `Network.reconnect`.
+ * All nodes in a network must have a unique address; the network will
+ * generate a unique address and assign it to new nodes. A node must never ever
+ * change its address on its own accord.
*
* Per default there are two kinds of nodes: tile entities and item components.
* If a `TileEntity` implements this interface adding/removal from its
@@ -20,8 +18,8 @@ import net.minecraft.nbt.NBTTagCompound
* All other kinds of nodes you may come up with will also have to be handled
* manually.
*
- * Items have to be handled by a corresponding `IItemDriver`. Existing
- * blocks may be interfaced with a proxy block if a `IBlockDriver` exists
+ * Items have to be handled by a corresponding `ItemDriver`. Existing
+ * blocks may be interfaced with a proxy block if a `BlockDriver` exists
* that supports the block.
*/
trait Node extends Persistable {
@@ -55,28 +53,24 @@ trait Node extends Persistable {
* The address of the node, so that it can be found in the network.
*
* This is used by the network manager when a node is added to a network to
- * assign it a unique address in that network. Nodes should avoid using custom
- * addresses since that could lead to multiple nodes with the same address in
- * a network. Note that that in and by itself is supported however, it is just
- * harder to work with.
- *
- * Some nodes may however wish to simply ignore this and always provide the
- * same address (e.g. zero), when they are never expected to be used by other
- * nodes (cables, for example).
+ * assign it a unique address, if it doesn't already have one. Nodes must not
+ * use custom addresses, only those assigned by the network. The only option
+ * they have is to *not* have an address, which can be useful for "dummy"
+ * nodes, such as cables. In that case they may ignore the address being set.
*
* @return the id of this node.
*/
- var address = 0
+ var address: Option[String] = None
/**
* The network this node is currently in.
*
- * Note that valid nodes should never return `null` here. When created a node
+ * Note that valid nodes should never return `None` here. When created a node
* should immediately be added to a network, after being removed from its
- * network it should be considered invalid.
+ * network a node should be considered invalid.
*
- * This is used by the `NetworkAPI` and the network itself when merging with
- * another network. You should never have to set this yourself.
+ * This will always be set automatically by the network manager. Do not
+ * change this value and do not return anything that it wasn't set to.
*
* @return the network the node is in.
*/
@@ -101,7 +95,6 @@ trait Node extends Persistable {
if (message.source == this) message.name match {
case "network.connect" => onConnect()
case "network.disconnect" => onDisconnect()
- case "network.reconnect" => onReconnect()
case _ => // Ignore.
}
None
@@ -109,31 +102,44 @@ trait Node extends Persistable {
/**
* Reads a previously stored address value from the specified tag.
- *
- * This should be called when implementing class is loaded.
+ *
+ * This should be called when associated object is loaded. For items, this
+ * should be called when their container is loaded. For blocks this should
+ * be called when their tile entity is loaded.
*
* @param nbt the tag to read from.
*/
def load(nbt: NBTTagCompound) = {
- network match {
- case None => address = nbt.getInteger("address")
- case Some(net) => net.reconnect(this, nbt.getInteger("address"))
- }
+ if (nbt.hasKey("address") && nbt.getTag("address").isInstanceOf[NBTTagString])
+ address = Option(nbt.getString("address"))
}
/**
* Stores the node's address in the specified NBT tag, to keep addresses the
* same across unloading/loading.
- *
- * This should be called when the implementing class is saved.
+ *
+ * This should be called when the implementing class is saved. For items,
+ * this should be called when their container is saved. For blocks this
+ * should be called when their tile entity is saved.
*
* @param nbt the tag to write to.
*/
- def save(nbt: NBTTagCompound) = nbt.setInteger("address", address)
+ def save(nbt: NBTTagCompound) = {
+ address.foreach(nbt.setString("address", _))
+ }
+ /**
+ * Called when this node is added to a network.
+ *
+ * Use this for custom initialization logic.
+ */
protected def onConnect() {}
+ /**
+ * Called when this node is removed from a network.
+ *
+ * Use this for custom tear-down logic. A node should be considered invalid
+ * and non-reusable after this has happened.
+ */
protected def onDisconnect() {}
-
- protected def onReconnect() {}
}
\ No newline at end of file
diff --git a/li/cil/oc/common/component/ScreenEnvironment.scala b/li/cil/oc/common/component/ScreenEnvironment.scala
index 082ef94fa..02fa05b25 100644
--- a/li/cil/oc/common/component/ScreenEnvironment.scala
+++ b/li/cil/oc/common/component/ScreenEnvironment.scala
@@ -15,7 +15,7 @@ trait ScreenEnvironment extends Node {
override def name = "screen"
- override def visibility = Visibility.Neighbors
+ override def visibility = Visibility.Network
override def receive(message: Message): Option[Array[Any]] = {
super.receive(message)
@@ -53,7 +53,7 @@ trait ScreenEnvironment extends Node {
}
def onScreenResolutionChange(w: Int, h: Int) =
- network.foreach(_.sendToAll(this, "computer.signal", "screen_resized", w, h))
+ network.foreach(_.sendToVisible(this, "computer.signal", "screen_resized", w, h))
def onScreenSet(col: Int, row: Int, s: String) {}
diff --git a/li/cil/oc/common/tileentity/Computer.scala b/li/cil/oc/common/tileentity/Computer.scala
index f4087be2c..eccfad371 100644
--- a/li/cil/oc/common/tileentity/Computer.scala
+++ b/li/cil/oc/common/tileentity/Computer.scala
@@ -32,14 +32,14 @@ class Computer(isClient: Boolean) extends Rotatable with ComputerEnvironment wit
message.data match {
// 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" && message.source.address != 0 && isRunning =>
- computer.signal("component_removed", message.source.address); None
+ case Array() if message.name == "network.connect" && isRunning =>
+ computer.signal("component_added", message.source.address.get); None
+ case Array() if message.name == "network.disconnect" && isRunning =>
+ computer.signal("component_removed", message.source.address.get); None
case Array(oldAddress: Integer) if message.name == "network.reconnect" && isRunning =>
- computer.signal("component_changed", message.source.address, oldAddress); None
+ computer.signal("component_changed", message.source.address.get, oldAddress); None
case Array(name: String, args@_*) if message.name == "computer.signal" =>
- computer.signal(name, List(message.source.address) ++ args: _*); None
+ computer.signal(name, List(message.source.address.get) ++ args: _*); None
case Array() if message.name == "computer.start" =>
Some(Array(turnOn().asInstanceOf[Any]))
case Array() if message.name == "computer.stop" =>
@@ -50,11 +50,6 @@ class Computer(isClient: Boolean) extends Rotatable with ComputerEnvironment wit
}
}
- override protected def onReconnect() = {
- super.onReconnect()
- computer.signal("address_change", address)
- }
-
// ----------------------------------------------------------------------- //
// General
// ----------------------------------------------------------------------- //
@@ -97,9 +92,9 @@ class Computer(isClient: Boolean) extends Rotatable with ComputerEnvironment wit
if (isRunning != computer.isRunning) {
isRunning = computer.isRunning
if (isRunning)
- network.foreach(_.sendToAll(this, "computer.started"))
+ network.foreach(_.sendToVisible(this, "computer.started"))
else
- network.foreach(_.sendToAll(this, "computer.stopped"))
+ network.foreach(_.sendToVisible(this, "computer.stopped"))
ServerPacketSender.sendComputerState(this, isRunning)
}
}
diff --git a/li/cil/oc/common/tileentity/Keyboard.scala b/li/cil/oc/common/tileentity/Keyboard.scala
index adba1b30d..c39011055 100644
--- a/li/cil/oc/common/tileentity/Keyboard.scala
+++ b/li/cil/oc/common/tileentity/Keyboard.scala
@@ -15,13 +15,13 @@ class Keyboard extends Rotatable with Node {
message.data match {
case Array(p: Player, char: Char, code: Int) if message.name == "keyboard.keyDown" =>
if (isUseableByPlayer(p))
- network.foreach(_.sendToAll(this, "computer.signal", "key_down", char, code))
+ network.foreach(_.sendToVisible(this, "computer.signal", "key_down", char, code))
case Array(p: Player, char: Char, code: Int) if message.name == "keyboard.keyUp" =>
if (isUseableByPlayer(p))
- network.foreach(_.sendToAll(this, "computer.signal", "key_up", char, code))
+ network.foreach(_.sendToVisible(this, "computer.signal", "key_up", char, code))
case Array(p: Player, value: String) if message.name == "keyboard.clipboard" =>
if (isUseableByPlayer(p))
- network.foreach(_.sendToAll(this, "computer.signal", "clipboard", value))
+ network.foreach(_.sendToVisible(this, "computer.signal", "clipboard", value))
case _ => // Ignore.
}
None
diff --git a/li/cil/oc/server/component/Computer.scala b/li/cil/oc/server/component/Computer.scala
index debf07249..032f01f23 100644
--- a/li/cil/oc/server/component/Computer.scala
+++ b/li/cil/oc/server/component/Computer.scala
@@ -159,7 +159,7 @@ class Computer(val owner: Computer.Environment) extends component.Computer with
signal("")
// Inject component added signals for all nodes in the network.
- owner.network.foreach(_.nodes(owner).foreach(node => signal("component_added", node.address)))
+ owner.network.foreach(_.nodes(owner).foreach(node => signal("component_added", node.address.get)))
// All green, computer started successfully.
true
@@ -242,12 +242,12 @@ class Computer(val owner: Computer.Environment) extends component.Computer with
// 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.foreach(_.sendToAll(owner, "computer.crashed", "not enough memory"))
+ //owner.network.foreach(_.sendToVisible(owner, "computer.crashed", "not enough memory"))
close()
case e: java.lang.Error if e.getMessage == "not enough memory" =>
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.foreach(_.sendToAll(owner, "computer.crashed", "not enough memory"))
+ //owner.network.foreach(_.sendToVisible(owner, "computer.crashed", "not enough memory"))
close()
case e: Throwable => {
OpenComputers.log.log(Level.WARNING, "Faulty Lua implementation for synchronized calls.", e)
@@ -488,7 +488,10 @@ class Computer(val owner: Computer.Environment) extends component.Computer with
// Allow the computer to figure out its own id in the component network.
lua.pushScalaFunction(lua => {
- lua.pushInteger(owner.address)
+ owner.address match {
+ case None => lua.pushNil()
+ case Some(address) => lua.pushString(address)
+ }
1
})
lua.setField(-2, "address")
@@ -555,7 +558,7 @@ class Computer(val owner: Computer.Environment) extends component.Computer with
lua.pushScalaFunction(lua =>
owner.network.fold(None: Option[Array[Any]])(_.
- sendToAddress(owner, lua.checkInteger(1), lua.checkString(2), parseArguments(lua, 3): _*)) match {
+ sendToAddress(owner, lua.checkString(1), lua.checkString(2), parseArguments(lua, 3): _*)) match {
case Some(Array(results@_*)) =>
results.foreach(pushResult(lua, _))
results.length
@@ -563,14 +566,14 @@ class Computer(val owner: Computer.Environment) extends component.Computer with
})
lua.setGlobal("sendToNode")
- lua.pushScalaFunction(lua => {
- owner.network.foreach(_.sendToAll(owner, lua.checkString(1), parseArguments(lua, 2): _*))
- 0
- })
- lua.setGlobal("sendToAll")
+// lua.pushScalaFunction(lua => {
+// owner.network.foreach(_.sendToVisible(owner, lua.checkString(1), parseArguments(lua, 2): _*))
+// 0
+// })
+// lua.setGlobal("sendToAll")
lua.pushScalaFunction(lua => {
- owner.network.fold(None: Option[Node])(_.node(lua.checkInteger(1))) match {
+ owner.network.fold(None: Option[Node])(_.node(lua.checkString(1))) match {
case None => 0
case Some(node) => lua.pushString(node.name); 1
}
diff --git a/li/cil/oc/server/component/Filesystem.scala b/li/cil/oc/server/component/Filesystem.scala
index f4623cf98..d5a2a531d 100644
--- a/li/cil/oc/server/component/Filesystem.scala
+++ b/li/cil/oc/server/component/Filesystem.scala
@@ -10,24 +10,19 @@ import scala.collection.mutable
class FileSystem(val fileSystem: api.FileSystem) extends ItemComponent {
- private val handles = mutable.Map.empty[Int, mutable.Set[Long]]
+ private val handles = mutable.Map.empty[String, mutable.Set[Long]]
override def name = "fs"
override def receive(message: Message) = {
message.data match {
- case Array() if message.name == "network.disconnect" && handles.contains(message.source.address) =>
- for (handle <- handles(message.source.address)) {
+ case Array() if message.name == "network.disconnect" && handles.contains(message.source.address.get) =>
+ for (handle <- handles(message.source.address.get)) {
fileSystem.file(handle) match {
case None => // Maybe file system was accessed from somewhere else.
case Some(file) => file.close()
}
}
- case Array(oldAddress: Int) if message.name == "network.reconnect" =>
- handles.remove(oldAddress) match {
- case None => // Node held no handles.
- case Some(set) => handles += message.source.address -> set
- }
case _ => // Ignore.
}
super.receive(message)
@@ -54,7 +49,7 @@ class FileSystem(val fileSystem: api.FileSystem) extends ItemComponent {
case Array(path: Array[Byte], mode: Array[Byte]) if message.name == "fs.open" =>
val handle = fileSystem.open(clean(path), Mode.parse(new String(mode, "UTF-8")))
if (handle > 0) {
- handles.getOrElseUpdate(message.source.address, mutable.Set.empty[Long]) += handle
+ handles.getOrElseUpdate(message.source.address.get, mutable.Set.empty[Long]) += handle
}
Some(Array(handle.asInstanceOf[Any]))
@@ -62,7 +57,7 @@ class FileSystem(val fileSystem: api.FileSystem) extends ItemComponent {
fileSystem.file(handle.toLong) match {
case None => // Ignore.
case Some(file) =>
- handles.get(message.source.address) match {
+ handles.get(message.source.address.get) match {
case None => // Not the owner of this handle.
case Some(set) => if (set.remove(handle.toLong)) file.close()
}
diff --git a/li/cil/oc/server/component/GraphicsCard.scala b/li/cil/oc/server/component/GraphicsCard.scala
index 80c8a410a..9f597aca3 100644
--- a/li/cil/oc/server/component/GraphicsCard.scala
+++ b/li/cil/oc/server/component/GraphicsCard.scala
@@ -9,29 +9,29 @@ class GraphicsCard extends ItemComponent {
override def name = "gpu"
override protected def receiveFromNeighbor(network: Network, message: Message) = message.data match {
- case Array(screen: Double, w: Double, h: Double) if message.name == "gpu.resolution=" =>
+ case Array(screen: Array[Byte], w: Double, h: Double) if message.name == "gpu.resolution=" =>
if (supportedResolutions.contains((w.toInt, h.toInt)))
- network.sendToAddress(this, screen.toInt, "screen.resolution=", w.toInt, h.toInt)
+ network.sendToAddress(this, new String(screen, "UTF-8"), "screen.resolution=", w.toInt, h.toInt)
else
Some(Array(Unit, "unsupported resolution"))
- case Array(screen: Double) if message.name == "gpu.resolution" =>
- network.sendToAddress(this, screen.toInt, "screen.resolution")
- case Array(screen: Double) if message.name == "gpu.resolutions" =>
- network.sendToAddress(this, screen.toInt, "screen.resolutions") match {
+ case Array(screen: Array[Byte]) if message.name == "gpu.resolution" =>
+ network.sendToAddress(this, new String(screen, "UTF-8"), "screen.resolution")
+ case Array(screen: Array[Byte]) if message.name == "gpu.resolutions" =>
+ network.sendToAddress(this, new String(screen, "UTF-8"), "screen.resolutions") match {
case Some(Array(resolutions@_*)) =>
Some(Array(supportedResolutions.intersect(resolutions): _*))
case _ => None
}
- case Array(screen: Double, x: Double, y: Double, value: Array[Byte]) if message.name == "gpu.set" =>
- network.sendToAddress(this, screen.toInt, "screen.set", x.toInt - 1, y.toInt - 1, new String(value, "UTF-8"))
- case Array(screen: Double, x: Double, y: Double, w: Double, h: Double, value: Array[Byte]) if message.name == "gpu.fill" =>
+ case Array(screen: Array[Byte], x: Double, y: Double, value: Array[Byte]) if message.name == "gpu.set" =>
+ network.sendToAddress(this, new String(screen, "UTF-8"), "screen.set", x.toInt - 1, y.toInt - 1, new String(value, "UTF-8"))
+ case Array(screen: Array[Byte], x: Double, y: Double, w: Double, h: Double, value: Array[Byte]) if message.name == "gpu.fill" =>
val s = new String(value, "UTF-8")
if (s.length == 1)
- network.sendToAddress(this, screen.toInt, "screen.fill", x.toInt - 1, y.toInt - 1, w.toInt, h.toInt, s.charAt(0))
+ network.sendToAddress(this, new String(screen, "UTF-8"), "screen.fill", x.toInt - 1, y.toInt - 1, w.toInt, h.toInt, s.charAt(0))
else
Some(Array(Unit, "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.sendToAddress(this, screen.toInt, "screen.copy", x.toInt - 1, y.toInt - 1, w.toInt, h.toInt, tx.toInt, ty.toInt)
+ case Array(screen: Array[Byte], x: Double, y: Double, w: Double, h: Double, tx: Double, ty: Double) if message.name == "gpu.copy" =>
+ network.sendToAddress(this, new String(screen, "UTF-8"), "screen.copy", x.toInt - 1, y.toInt - 1, w.toInt, h.toInt, tx.toInt, ty.toInt)
case _ => None
}
}
\ No newline at end of file
diff --git a/li/cil/oc/server/component/RedstoneCard.scala b/li/cil/oc/server/component/RedstoneCard.scala
index 9a9a778a6..d5023f916 100644
--- a/li/cil/oc/server/component/RedstoneCard.scala
+++ b/li/cil/oc/server/component/RedstoneCard.scala
@@ -10,18 +10,18 @@ class RedstoneCard extends ItemComponent {
override def name = "redstone"
override protected def receiveFromNeighbor(network: Network, message: 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 Array(target: Array[Byte], side: Double) if message.name == "redstone.input" =>
+ input(new String(target, "UTF-8"), side.toInt)
+ case Array(target: Array[Byte], side: Double) if message.name == "redstone.output" =>
+ output(new String(target, "UTF-8"), side.toInt)
+ case Array(target: Array[Byte], side: Double, value: Double) if message.name == "redstone.output=" =>
+ output(new String(target, "UTF-8"), side.toInt, value.toInt); None
case _ => None // Ignore.
}
- private def tryGet(target: Int) = network.fold(None: Option[Node])(_.node(target))
+ private def tryGet(target: String) = network.fold(None: Option[Node])(_.node(target))
- private def input(target: Int, side: Int) = if (side >= 0 && side < 6) tryGet(target) match {
+ private def input(target: String, side: Int) = if (side >= 0 && side < 6) tryGet(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)
@@ -31,7 +31,7 @@ class RedstoneCard extends ItemComponent {
case _ => None // Can't work with this node.
} else None
- private def output(target: Int, side: Int) = if (side >= 0 && side < 6) tryGet(target) match {
+ private def output(target: String, side: Int) = if (side >= 0 && side < 6) tryGet(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)
@@ -39,7 +39,7 @@ class RedstoneCard extends ItemComponent {
case _ => None // Can't work with this node.
} else None
- private def output(target: Int, side: Int, value: Int) = if (side >= 0 && side < 6) tryGet(target) match {
+ private def output(target: String, side: Int, value: Int) = if (side >= 0 && side < 6) tryGet(target) match {
case Some(r: RedstoneEnabled) => r.output(ForgeDirection.getOrientation(side)) = value
case _ => // Can't work with this node.
}
diff --git a/li/cil/oc/server/network/Network.scala b/li/cil/oc/server/network/Network.scala
index 7d29e6373..841349b71 100644
--- a/li/cil/oc/server/network/Network.scala
+++ b/li/cil/oc/server/network/Network.scala
@@ -1,52 +1,39 @@
package li.cil.oc.server.network
-import _root_.net.minecraft.block.Block
-import _root_.net.minecraft.tileentity.TileEntity
-import _root_.net.minecraft.world.{IBlockAccess, World}
-import _root_.net.minecraftforge.common.ForgeDirection
-import _root_.net.minecraftforge.event.ForgeSubscribe
-import _root_.net.minecraftforge.event.world.ChunkEvent
import java.util.logging.Level
import li.cil.oc.api.network.Visibility
-import li.cil.oc.api.{network => net}
-import li.cil.oc.server.network.Network.Node
import li.cil.oc.{api, OpenComputers}
+import net.minecraft.block.Block
+import net.minecraft.tileentity.TileEntity
+import net.minecraft.world.{IBlockAccess, World}
+import net.minecraftforge.common.ForgeDirection
+import net.minecraftforge.event.ForgeSubscribe
+import net.minecraftforge.event.world.ChunkEvent
import scala.beans.BeanProperty
import scala.collection.JavaConverters._
import scala.collection.mutable
import scala.collection.mutable.ArrayBuffer
-/**
- * Network implementation for component networks.
- *
- * This network interconnects components in a geometry-agnostic fashion. It
- * builds an internal graph of network nodes and the connections between them,
- * and takes care of merges when adding connections, as well as net splits on
- * node removal.
- *
- * It keeps the list of nodes as a lookup table for fast id->node resolving.
- * Note that it is possible for multiple nodes to have the same ID, though.
- */
-class Network private(private val nodeMap: mutable.Map[Int, ArrayBuffer[Network.Node]]) extends api.Network {
- def this(node: net.Node) = {
- this(mutable.Map({
- if (node.address < 1)
- node.address = 1
- node.address -> ArrayBuffer(new Network.Node(node))
- }))
- send(new Network.ConnectMessage(node), List(node))
+class Network private(private val addressedNodes: mutable.Map[String, Network.Node] = mutable.Map.empty,
+ private val unaddressedNodes: mutable.ArrayBuffer[Network.Node] = mutable.ArrayBuffer.empty) extends api.Network {
+ def this(node: api.network.Node) = {
+ this()
+ addNew(node)
+ send(Network.ConnectMessage(node), Iterable(node))
}
nodes.foreach(_.network = Some(this))
- def connect(nodeA: net.Node, nodeB: net.Node) = {
- val containsA = nodeMap.get(nodeA.address).exists(_.exists(_.data == nodeA))
- val containsB = nodeMap.get(nodeB.address).exists(_.exists(_.data == nodeB))
+ def connect(nodeA: api.network.Node, nodeB: api.network.Node) = {
+ val containsA = contains(nodeA)
+ val containsB = contains(nodeB)
+
if (!containsA && !containsB) throw new IllegalArgumentException(
"At least one of the nodes must already be in this network.")
- def oldNodeA = nodeMap(nodeA.address).find(_.data == nodeA).get
- def oldNodeB = nodeMap(nodeB.address).find(_.data == nodeB).get
+ def oldNodeA = node(nodeA)
+ def oldNodeB = node(nodeB)
+
if (containsA && containsB) {
// Both nodes already exist in the network but there is a new connection.
// This can happen if a new node sequentially connects to multiple nodes
@@ -58,68 +45,169 @@ class Network private(private val nodeMap: mutable.Map[Int, ArrayBuffer[Network.
Network.Edge(oldNodeA, oldNodeB)
true
}
- // That connection already exists.
- else false
+ else false // That connection already exists.
}
- // New node for this network, order the nodes and add the new one.
else if (containsA) add(oldNodeA, nodeB)
else add(oldNodeB, nodeA)
}
- private def add(oldNode: Network.Node, addedNode: net.Node) = {
+ def disconnect(nodeA: api.network.Node, nodeB: api.network.Node) = {
+ val containsA = contains(nodeA)
+ val containsB = contains(nodeB)
+
+ if (!containsA || !containsB) throw new IllegalArgumentException(
+ "Both of the nodes must be in this network.")
+
+ def oldNodeA = node(nodeA)
+ def oldNodeB = node(nodeB)
+
+ oldNodeA.edges.find(_.isBetween(oldNodeA, oldNodeB)) match {
+ case None => false // That connection doesn't exists.
+ case Some(edge) => {
+ handleSplit(edge.remove())
+ if (edge.left.data.visibility == Visibility.Neighbors)
+ send(Network.DisconnectMessage(edge.left.data), Iterable(edge.right.data))
+ if (edge.right.data.visibility == Visibility.Neighbors)
+ send(Network.DisconnectMessage(edge.right.data), Iterable(edge.left.data))
+ true
+ }
+ }
+ }
+
+ def remove(node: api.network.Node) = (node.address match {
+ case None => unaddressedNodes.indexWhere(_.data == node) match {
+ case -1 => None
+ case index => Some(unaddressedNodes.remove(index))
+ }
+ case Some(address) => addressedNodes.remove(address)
+ }) match {
+ case None => false
+ case Some(entry) => {
+ node.network = None
+ val subGraphs = entry.remove()
+ val targets = Iterable(node) ++ (entry.data.visibility match {
+ case Visibility.None => Iterable.empty[api.network.Node]
+ case Visibility.Neighbors => entry.edges.map(_.other(entry).data)
+ case Visibility.Network => subGraphs.map {
+ case (addressed, unaddressed) => addressed.values.map(_.data)
+ }.flatten
+ }).filter(_.visibility != Visibility.None)
+ handleSplit(subGraphs)
+ send(Network.DisconnectMessage(node), targets)
+ true
+ }
+ }
+
+ def node(address: String) = addressedNodes.get(address) match {
+ case Some(node) => Some(node.data)
+ case _ => None
+ }
+
+ def nodes = addressedNodes.values.map(_.data).filter(_.visibility != Visibility.None)
+
+ def nodes(reference: api.network.Node) = {
+ val referenceNeighbors = neighbors(reference).toSet + reference
+ nodes.filter(node => node.visibility == Visibility.Network || referenceNeighbors.contains(node))
+ }
+
+ def neighbors(node: api.network.Node) = (node.address match {
+ case None =>
+ unaddressedNodes.find(_.data == node) match {
+ case None => throw new IllegalArgumentException("Node must be in this network.")
+ case Some(n) => n.edges.map(_.other(n).data)
+ }
+ case Some(address) =>
+ addressedNodes.get(address) match {
+ case None => throw new IllegalArgumentException("Node must be in this network.")
+ case Some(n) => n.edges.map(_.other(n).data)
+ }
+ }).filter(_.visibility != Visibility.None)
+
+ def sendToAddress(source: api.network.Node, target: String, name: String, data: Any*) = {
+ if (source.network.isEmpty || source.network.get != this)
+ throw new IllegalArgumentException("Source node must be in this network.")
+ if (source.address.isDefined) addressedNodes.get(target) match {
+ case Some(node) if node.data.visibility == Visibility.Network ||
+ (node.data.visibility == Visibility.Neighbors && neighbors(node.data).exists(_ == source)) =>
+ send(new Network.Message(source, name, Array(data: _*)), Iterable(node.data))
+ case _ => None
+ } else None
+ }
+
+ def sendToNeighbors(source: api.network.Node, name: String, data: Any*) = {
+ if (source.network.isEmpty || source.network.get != this)
+ throw new IllegalArgumentException("Source node must be in this network.")
+ if (source.address.isDefined)
+ send(new Network.Message(source, name, Array(data: _*)), neighbors(source))
+ }
+
+ def sendToVisible(source: api.network.Node, name: String, data: Any*) = {
+ if (source.network.isEmpty || source.network.get != this)
+ throw new IllegalArgumentException("Source node must be in this network.")
+ if (source.address.isDefined)
+ send(new Network.Message(source, name, Array(data: _*)), nodes(source))
+ }
+
+ private def contains(node: api.network.Node) = (node.address match {
+ case None => unaddressedNodes.find(_.data == node)
+ case Some(address) => addressedNodes.get(address)
+ }).exists(_.data == node)
+
+ private def node(node: api.network.Node) = (node.address match {
+ case None => unaddressedNodes.find(_.data == node)
+ case Some(address) => addressedNodes.get(address)
+ }).get
+
+ private def addNew(node: api.network.Node) = {
+ val newNode = new Network.Node(node)
+ if (node.address.isEmpty)
+ node.address = Some(java.util.UUID.randomUUID().toString)
+ if (node.address.isDefined)
+ addressedNodes += node.address.get -> newNode
+ else
+ unaddressedNodes += newNode
+ node.network = Some(this)
+ newNode
+ }
+
+ private def add(oldNode: Network.Node, addedNode: api.network.Node) = {
// Check if the other node is new or if we have to merge networks.
- val (newNode, sendQueue) = if (addedNode.network.isEmpty) {
- val newNode = new Network.Node(addedNode)
- if (nodeMap.contains(addedNode.address) || addedNode.address < 1)
- addedNode.address = findId()
- // Store everything with an invalid address in slot zero.
- val address = addedNode.address match {
- case a if a > 0 => a
- case _ => 0
+ val sendQueue = mutable.Buffer.empty[(Network.Message, Iterable[api.network.Node])]
+ val (newNode) = if (addedNode.network.isEmpty) {
+ val newNode = addNew(addedNode)
+ if (addedNode.address.isDefined) addedNode.visibility match {
+ case Visibility.None => // Nothing to do.
+ case Visibility.Neighbors =>
+ sendQueue += ((Network.ConnectMessage(addedNode), Iterable(addedNode) ++ neighbors(addedNode)))
+ nodes(addedNode).foreach(node => sendQueue += ((new Network.ConnectMessage(node), Iterable(addedNode))))
+ case Visibility.Network =>
+ sendQueue += ((Network.ConnectMessage(addedNode), nodes))
+ nodes(addedNode).foreach(node => if (node != addedNode)
+ sendQueue += ((new Network.ConnectMessage(node), Iterable(addedNode))))
}
- // 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[net.Node])]
- 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 = Some(this)
- (newNode, sendQueue)
+ newNode
}
else {
// Queue any messages to avoid side effects from receivers.
- val sendQueue = mutable.Buffer.empty[(Network.Message, Iterable[net.Node])]
val thisNodes = nodes.toBuffer
val otherNetwork = addedNode.network.get.asInstanceOf[Network]
val otherNodes = otherNetwork.nodes.toBuffer
otherNodes.foreach(node => sendQueue += ((Network.ConnectMessage(node), thisNodes)))
thisNodes.foreach(node => sendQueue += ((Network.ConnectMessage(node), otherNodes)))
- // 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
- node.address = findId(reserved)
- if (node.address != oldAddress) {
- reserved += node.address
- // Prepend to notify old nodes of address changes first.
- sendQueue.+=:((Network.ReconnectMessage(node, oldAddress), otherNodes))
- }
- })
-
- // 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
+ // Add nodes from other network into this network.
+ otherNetwork.addressedNodes.values.foreach(node => {
+ addressedNodes += node.data.address.get -> node
node.data.network = Some(this)
})
+ unaddressedNodes ++= otherNetwork.unaddressedNodes
+ otherNetwork.unaddressedNodes.foreach(_.data.network = Some(this))
// Return the node object of the newly connected node for the next step.
- (nodeMap(addedNode.address).find(_.data == addedNode).get, sendQueue)
+ addedNode.address match {
+ case None => unaddressedNodes.find(_.data == addedNode).get
+ case Some(address) => addressedNodes(address)
+ }
}
// Add the connection between the two nodes.
@@ -131,195 +219,66 @@ class Network private(private val nodeMap: mutable.Map[Int, ArrayBuffer[Network.
true
}
- def reconnect(node: net.Node, address: Int): Boolean = {
- if (!nodeMap.get(node.address).exists(_.exists(_.data == node))) throw new IllegalArgumentException(
- "The node must already be in this network.")
-
- val oldAddress = node.address
- if (address == oldAddress) false
- else {
- val otherMessage = if (address < 1) {
- node.address = 0
- None
+ private def handleSplit(subGraphs: Seq[(mutable.Map[String, Network.Node], mutable.ArrayBuffer[Network.Node])]) =
+ if (subGraphs.size > 1) {
+ val subNodes = subGraphs.map {
+ case (addressed, unaddressed) =>
+ (addressed.values ++ unaddressed).map(_.data).filter(_.visibility != Visibility.None)
+ }
+ val visibleNodes = subGraphs.map {
+ case (addressed, unaddressed) =>
+ addressed.values.map(_.data).filter(_.visibility == Visibility.Network)
}
- else {
- // Check if there's a simple collision, if so resolve it.
- nodeMap.get(address) match {
- case None =>
- // No collision.
- node.address = address
- None
- case Some(otherList) =>
- if (otherList.size > 1)
- return false // Already multiple nodes with that address...
- else {
- // Simple collision.
- val other = otherList.head
- otherList -= other
- other.data.address = findId()
- nodeMap.getOrElseUpdate(other.data.address, new mutable.ArrayBuffer[Node]) += other
- Some((Network.ReconnectMessage(other.data, address), nodes))
- }
+ addressedNodes.clear()
+ unaddressedNodes.clear()
+
+ subGraphs.head match {
+ case (addressed, unaddressed) =>
+ addressedNodes ++= addressed
+ unaddressedNodes ++= unaddressed
+ }
+
+ subGraphs.tail.foreach {
+ case (addressed, unaddressed) =>
+ new Network(addressed, unaddressed)
+ }
+
+ for (indexA <- 0 until visibleNodes.length) {
+ val nodesA = subNodes(indexA)
+ val visibleNodesA = visibleNodes(indexA)
+ for (indexB <- (indexA + 1) until visibleNodes.length) {
+ val nodesB = subNodes(indexB)
+ val visibleNodesB = visibleNodes(indexB)
+ visibleNodesA.foreach(nodeA => send(new Network.DisconnectMessage(nodeA), nodesB))
+ visibleNodesB.foreach(nodeB => send(new Network.DisconnectMessage(nodeB), nodesA))
}
}
-
- val oldList = nodeMap(oldAddress)
- val innerNode = oldList.remove(oldList.indexWhere(_.data == node))
- if (oldList.isEmpty)
- nodeMap -= oldAddress
- nodeMap.getOrElseUpdate(node.address, new mutable.ArrayBuffer[Node]) += innerNode
-
- otherMessage.foreach {
- case (message, targets) => send(message, targets)
- }
- send(Network.ReconnectMessage(node, oldAddress), nodes)
-
- true
- }
- }
-
- def disconnect(nodeA: net.Node, nodeB: net.Node) = {
- val containsA = nodeMap.get(nodeA.address).exists(_.exists(_.data == nodeA))
- val containsB = nodeMap.get(nodeB.address).exists(_.exists(_.data == nodeB))
- if (!containsA || !containsB) throw new IllegalArgumentException(
- "Both of the nodes must be in this network.")
-
- def oldNodeA = nodeMap(nodeA.address).find(_.data == nodeA).get
- def oldNodeB = nodeMap(nodeB.address).find(_.data == nodeB).get
- oldNodeA.edges.find(_.isBetween(oldNodeA, oldNodeB)) match {
- case None => false // That connection doesn't exists.
- case Some(edge) => {
- handleSplit(edge.remove())
- true
- }
- }
- }
-
- def remove(node: net.Node) = nodeMap.get(node.address) match {
- case None => false
- case Some(list) => list.find(_.data == node) match {
- case None => false
- case Some(entry) => {
- node.network = None
-
- // Removing a node may result in a net split, leaving us with multiple
- // networks. The remove function returns all resulting networks, one
- // 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 => send(new Network.DisconnectMessage(n), List(node)))
- send(new Network.DisconnectMessage(node), nodes)
- })
- send(new Network.DisconnectMessage(node), List(node))
- true
- }
- }
- }
-
- private def handleSplit(subGraphs: Seq[mutable.Map[Int, ArrayBuffer[Network.Node]]],
- messageCallback: Iterable[net.Node] => Unit = _ => {}) = {
- // Sending the removal messages can have side effects, so we'll keep a
- // copy of the original list of nodes in each sub network.
- val subNodes = subGraphs.map(_.values.flatten.map(_.data).toBuffer).toBuffer
-
- // We re-use this network by assigning the first sub graph to it. For
- // all additional sub graphs (if any) we'll have to create new ones.
- nodeMap.clear()
- // Empty for the last node removed from a network.
- if (!subGraphs.isEmpty) {
- nodeMap ++= subGraphs.head
- subGraphs.tail.foreach(new Network(_))
}
- // Send removal messages. First, to the removed node itself (for its
- // onDisconnect handler), then one for the removed node to all sub
- // networks, for each node in the sub networks back to the removed node
- // and if there was a net split (we have multiple networks) also for
- // each node now longer belonging to one of the resulting sub networks.
- for (indexA <- 0 until subNodes.length) {
- val nodesA = subNodes(indexA)
- for (indexB <- (indexA + 1) until subNodes.length) {
- val nodesB = subNodes(indexB)
- 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 Some(list) if address > 0 => list.map(_.data).filter(_.visibility != Visibility.None).lastOption
- case _ => None
- }
-
- def nodes(reference: net.Node) = {
- 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: net.Node) = nodeMap.get(node.address) match {
- case None => throw new IllegalArgumentException("Node must be in this network.")
- case Some(list) => list.find(_.data == node) match {
- case None => throw new IllegalArgumentException("Node must be in this network.")
- case Some(n) => n.edges.map(_.other(n).data)
- }
- }
-
- def sendToAddress(source: net.Node, target: Int, name: String, data: Any*) =
- nodeMap.get(target) match {
- case None => None
- case Some(list) => send(new Network.Message(source, name, Array(data: _*)), list.map(_.data))
+ private def send(message: Network.Message, targets: Iterable[api.network.Node]) = {
+ def protectedSend(target: api.network.Node) = try {
+ //println("receive(" + message.name + "(" + message.data.mkString(", ") + "): " + message.source.address.get + ":" + message.source.name + " -> " + target.address.get + ":" + target.name + ")")
+ target.receive(message)
+ } catch {
+ case e: Throwable => OpenComputers.log.log(Level.WARNING, "Error in message handler", e); None
}
- def sendToNeighbors(source: net.Node, name: String, data: Any*) =
- send(new Network.Message(source, name, Array(data: _*)), neighbors(source))
-
- def sendToAll(source: net.Node, name: String, data: Any*) =
- send(new Network.Message(source, name, Array(data: _*)), nodes)
-
- private def send(message: Network.Message, targets: Iterable[net.Node]) =
- if (message.source.address > 0 && message.source.visibility != Visibility.None) {
- def protectedSend(target: net.Node) = try {
- //println("receive(" + message.name + "(" + message.data.mkString(", ") + "): " + message.source.address + ":" + message.source.name + " -> " + target.address + ":" + target.name + ")")
- target.receive(message)
- } catch {
- case e: Throwable => OpenComputers.log.log(Level.WARNING, "Error in message handler", e); None
- }
-
- message match {
- case _@(Network.ConnectMessage(_) | Network.ReconnectMessage(_, _)) =>
- // Cannot be canceled but respects visibility.
- (message.source.visibility match {
- case Visibility.Neighbors =>
- // Note: the neighbors() call already filters out invalid nodes.
- val neighborSet = neighbors(message.source).toSet
- targets.filter(target => target == message.source || neighborSet.contains(target))
- case Visibility.Network =>
- targets.filter(_.address > 0).filter(_.visibility == Visibility.Network)
- }).foreach(protectedSend)
- None
- case _@Network.DisconnectMessage(_) =>
- // Cannot be canceled but ignores visibility (because it'd be a pain to implement this otherwise).
- targets.filter(_.address > 0).foreach(protectedSend)
- 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)
- protectedSend(iterator.next()) match {
- case None => // Ignore.
- case r => result = r
- }
- 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
+ message match {
+ case _@(Network.ConnectMessage(_) | Network.DisconnectMessage(_)) =>
+ targets.foreach(protectedSend)
+ None
+ case _ =>
+ var result = None: Option[Array[Any]]
+ val iterator = targets.iterator
+ while (!message.isCanceled && iterator.hasNext)
+ protectedSend(iterator.next()) match {
+ case None => // Ignore.
+ case r => result = r
+ }
+ result
+ }
+ }
}
object Network extends api.detail.NetworkAPI {
@@ -334,8 +293,8 @@ object Network extends api.detail.NetworkAPI {
private def onUnload(w: World, tileEntities: Iterable[TileEntity]) = if (!w.isRemote) {
// TODO add a more efficient batch remove operation? something along the lines of if #remove > #nodes*factor remove all, re-add remaining?
tileEntities.
- filter(_.isInstanceOf[net.Node]).
- map(_.asInstanceOf[net.Node]).
+ filter(_.isInstanceOf[api.network.Node]).
+ map(_.asInstanceOf[api.network.Node]).
foreach(t => t.network.foreach(_.remove(t)))
}
@@ -360,17 +319,17 @@ object Network extends api.detail.NetworkAPI {
}
}
- private def getNetworkNode(world: IBlockAccess, x: Int, y: Int, z: Int): Option[TileEntity with net.Node] =
+ private def getNetworkNode(world: IBlockAccess, x: Int, y: Int, z: Int): Option[TileEntity with api.network.Node] =
Option(Block.blocksList(world.getBlockId(x, y, z))) match {
case Some(block) if block.hasTileEntity(world.getBlockMetadata(x, y, z)) =>
world.getBlockTileEntity(x, y, z) match {
- case tileEntity: TileEntity with net.Node => Some(tileEntity)
+ case tileEntity: TileEntity with api.network.Node => Some(tileEntity)
case _ => None
}
case _ => None
}
- private class Node(val data: net.Node) {
+ private class Node(val data: api.network.Node) {
val edges = ArrayBuffer.empty[Edge]
def remove() = {
@@ -397,41 +356,35 @@ object Network extends api.detail.NetworkAPI {
private def searchGraphs(seeds: Seq[Node]) = {
val seen = mutable.Set.empty[Node]
seeds.map(seed => {
- if (seen.contains(seed)) {
- // If our seed node is contained in another sub graph we have nothing
- // to do, since we're a sub graph of that sub graph.
- mutable.Map.empty[Int, mutable.ArrayBuffer[Node]]
- }
+ if (seen.contains(seed)) None
else {
- // Not yet processed, start growing a network from here. We're
- // guaranteed to not find previously processed nodes, since edges
- // are bidirectional, and we'd be in the other branch otherwise.
- seen += seed
- val subGraph = mutable.Map(seed.data.address -> mutable.ArrayBuffer(seed))
- val queue = mutable.Queue(seed.edges.map(_.other(seed)): _*)
+ val addressed = mutable.Map.empty[String, Node]
+ val unaddressed = mutable.ArrayBuffer.empty[Node]
+ val queue = mutable.Queue(seed)
while (queue.nonEmpty) {
val node = queue.dequeue()
seen += node
- subGraph.getOrElseUpdate(node.data.address, new ArrayBuffer[Node]) += node
+ node.data.address match {
+ case None => unaddressed += node
+ case Some(address) => addressed += address -> node
+ }
queue ++= node.edges.map(_.other(node)).filter(n => !seen.contains(n) && !queue.contains(n))
}
- subGraph
+ Some((addressed, unaddressed))
}
- }) filter (_.nonEmpty)
+ }) filter (_.nonEmpty) map (_.get)
}
- private class Message(@BeanProperty val source: net.Node,
+ private class Message(@BeanProperty val source: api.network.Node,
@BeanProperty val name: String,
- @BeanProperty val data: Array[Any] = Array()) extends net.Message {
+ @BeanProperty val data: Array[Any] = Array()) extends api.network.Message {
var isCanceled = false
def cancel() = isCanceled = true
}
- private case class ConnectMessage(override val source: net.Node) extends Message(source, "network.connect")
+ private case class ConnectMessage(override val source: api.network.Node) extends Message(source, "network.connect")
- private case class DisconnectMessage(override val source: net.Node) extends Message(source, "network.disconnect")
-
- private case class ReconnectMessage(override val source: net.Node, oldAddress: Int) extends Message(source, "network.reconnect", Array(oldAddress.asInstanceOf[Any]))
+ private case class DisconnectMessage(override val source: api.network.Node) extends Message(source, "network.disconnect")
}
\ No newline at end of file