network rewrite to use uuids as node addresses. this way we won't have to expect addresses to change on network merges, which make a lot of things a much easier (in particular on the Lua side)

This commit is contained in:
Florian Nücke 2013-10-01 22:10:54 +02:00
parent 06eac9254c
commit 59febb056f
15 changed files with 466 additions and 569 deletions

View File

@ -1,4 +1,3 @@
--[[ API for graphics cards. ]]
driver.gpu = {} driver.gpu = {}
function driver.gpu.setResolution(gpu, screen, w, h) 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) sendToNode(gpu, "gpu.copy", screen, col, row, w, h, tx, ty)
end end
function driver.gpu.bind(gpuId, screenId) function driver.gpu.bind(gpu, screen)
return { return {
setResolution = function(w, h) setResolution = function(w, h)
driver.gpu.setResolution(component.address(gpuId), component.address(screenId), w, h) driver.gpu.setResolution(gpu, screen, w, h)
end, end,
getResolution = function() getResolution = function()
return driver.gpu.getResolution(component.address(gpuId), component.address(screenId)) return driver.gpu.getResolution(gpu, screen)
end, end,
getResolutions = function() getResolutions = function()
return driver.gpu.getResolutions(component.address(gpuId), component.address(screenId)) return driver.gpu.getResolutions(gpu, screen)
end, end,
set = function(col, row, value) 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, end,
fill = function(col, ro, w, h, value) 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, end,
copy = function(col, row, w, h, tx, ty) 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
} }
end end

View File

@ -1,4 +1,3 @@
--[[ API for keyboards. ]]
driver.keyboard = {} driver.keyboard = {}
driver.keyboard.keys = { driver.keyboard.keys = {

View File

@ -1,6 +1,5 @@
--[[ API for Redstone Cards. ]]
driver.redstone = {} driver.redstone = {}
driver.rs = driver.redstone
driver.redstone.sides = {"top", "bottom", "left", "right", "front", "back"} driver.redstone.sides = {"top", "bottom", "left", "right", "front", "back"}

View File

@ -37,7 +37,7 @@ local weakListeners = {}
local function listenersFor(name, weak) local function listenersFor(name, weak)
checkArg(1, name, "string") checkArg(1, name, "string")
if weak then if weak then
weakListeners[name] = weakListeners[name] or setmetatable({}, {__mode = "k"}) weakListeners[name] = weakListeners[name] or setmetatable({}, {__mode = "v"})
return weakListeners[name] return weakListeners[name]
else else
listeners[name] = listeners[name] or {} listeners[name] = listeners[name] or {}
@ -52,13 +52,21 @@ local timers = {}
--[[ Register a new event listener for the specified event. ]] --[[ Register a new event listener for the specified event. ]]
function event.listen(name, callback, weak) function event.listen(name, callback, weak)
checkArg(2, callback, "function") checkArg(2, callback, "function")
listenersFor(name, weak)[callback] = true table.insert(listenersFor(name, weak), callback)
end end
--[[ Remove an event listener. ]] --[[ Remove an event listener. ]]
function event.ignore(name, callback) function event.ignore(name, callback)
listenersFor(name, false)[callback] = nil local function remove(list)
listenersFor(name, true)[callback] = nil 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 end
--[[ Dispatch an event with the specified parameter. ]] --[[ 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()). -- timer check (for example if we had no signal in coroutine.sleep()).
if name then if name then
checkArg(1, name, "string") 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, ...) local result, message = xpcall(callback, event.error, name, ...)
if not result and message then if not result and message then
error(message, 0) error(message, 0)
end end
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, ...) local result, message = xpcall(callback, event.error, name, ...)
if not result and message then if not result and message then
error(message, 0) error(message, 0)
@ -133,100 +141,64 @@ end
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
--[[ Keep track of connected components across address changes. ]] --[[ Keep track of connected components. ]]
local components = {} local components = {}
component = {} component = {}
function component.address(id) function component.type(address)
local component = components[id] local component = components[address]
if component then if component then
return component.address return component
end end
end end
function component.type(id) function component.list()
local component = components[id] local address = nil
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
return function() return function()
id = next(components, id) address = next(components, address)
return id return address
end end
end end
event.listen("component_added", function(_, address) event.listen("component_added", function(_, address)
local id = #components + 1 components[address] = driver.componentType(address)
components[id] = {address = address, name = driver.componentType(address)}
event.fire("component_installed", id)
end) end)
event.listen("component_removed", function(_, address) event.listen("component_removed", function(_, address)
local id = component.id(address) components[address] = nil
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
end) end)
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
--[[ Setup terminal API. ]] --[[ Setup terminal API. ]]
local gpuId, screenId = 0, 0 local gpuAddress, screenAddress = false, false
local screenWidth, screenHeight = 0, 0 local screenWidth, screenHeight = 0, 0
local boundGpu = nil local boundGpu = nil
local cursorX, cursorY = 1, 1 local cursorX, cursorY = 1, 1
event.listen("component_installed", function(_, id) event.listen("component_added", function(_, address)
local type = component.type(id) local type = component.type(address)
if type == "gpu" and gpuId < 1 then if type == "gpu" and not gpuAddress then
term.gpuId(id) term.gpu(address)
elseif type == "screen" and screenId < 1 then elseif type == "screen" and not screenAddress then
term.screenId(id) term.screen(address)
end end
end) end)
event.listen("component_uninstalled", function(_, id) event.listen("component_removed", function(_, address)
if gpuId == id then if gpuAddress == address then
term.gpuId(0) term.gpu(false)
for id in component.ids() do for address in component.list() do
if component.type(id) == "gpu" then if component.type(address) == "gpu" then
term.gpuId(id) term.gpu(address)
return return
end end
end end
elseif screenId == id then elseif screenAddress == address then
term.screenId(0) term.screen(false)
for id in component.ids() do for address in component.list() do
if component.type(id) == "screen" then if component.type(address) == "screen" then
term.screenId(id) term.screen(address)
return return
end end
end end
@ -234,17 +206,16 @@ event.listen("component_uninstalled", function(_, id)
end) end)
event.listen("screen_resized", function(_, address, w, h) event.listen("screen_resized", function(_, address, w, h)
local id = component.id(address) if address == screenAddress then
if id == screenId then
screenWidth = w screenWidth = w
screenHeight = h screenHeight = h
end end
end) end)
local function bindIfPossible() local function bindIfPossible()
if gpuId > 0 and screenId > 0 then if gpuAddress and screenAddress then
if not boundGpu then if not boundGpu then
boundGpu = driver.gpu.bind(gpuId, screenId) boundGpu = driver.gpu.bind(gpuAddress, screenAddress)
screenWidth, screenHeight = boundGpu.getResolution() screenWidth, screenHeight = boundGpu.getResolution()
event.fire("term_available") event.fire("term_available")
end end
@ -257,30 +228,26 @@ end
term = {} term = {}
function term.gpu()
return boundGpu
end
function term.screenSize() function term.screenSize()
return screenWidth, screenHeight return screenWidth, screenHeight
end end
function term.gpuId(id) function term.gpu(address)
if id then if address ~= nil then
checkArg(1, id, "number") checkArg(1, address, "string", "boolean")
gpuId = id gpuAddress = address
bindIfPossible() bindIfPossible()
end end
return gpuId return gpuAddress
end end
function term.screenId(id) function term.screen(address)
if id then if address ~= nil then
checkArg(1, id, "number") checkArg(1, address, "string", "boolean")
screenId = id screenAddress = address
bindIfPossible() bindIfPossible()
end end
return screenId return screenAddress
end end
function term.getCursor() function term.getCursor()
@ -359,23 +326,23 @@ end
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
--[[ Primitive command line. ]] --[[ Primitive command line. ]]
local keyboardId = 0 local keyboardAddress = false
local lastCommand, command = "", "" local lastCommand, command = "", ""
local isRunning = false local isRunning = false
event.listen("component_installed", function(_, id) event.listen("component_added", function(_, address)
local type = component.type(id) local type = component.type(address)
if type == "keyboard" and keyboardId < 1 then if type == "keyboard" and not keyboardAddress then
keyboardId = id term.keyboardAddress(address)
end end
end) end)
event.listen("component_uninstalled", function(_, id) event.listen("component_uninstalled", function(_, address)
if keyboardId == id then if keyboardAddress == address then
keyboardId = 0 term.keyboardAddress(false)
for id in component.ids() do for address in component.list() do
if component.type(id) == "keyboard" then if component.type(address) == "keyboard" then
keyboardId = id term.keyboardAddress(address)
return return
end end
end end
@ -383,17 +350,17 @@ event.listen("component_uninstalled", function(_, id)
end) end)
-- Put this into the term table since other programs may want to use it, too. -- Put this into the term table since other programs may want to use it, too.
function term.keyboardId(id) function term.keyboardAddress(address)
if id then if address ~= nil then
checkArg(1, id, "number") checkArg(1, address, "string", "boolean")
keyboardId = id keyboardAddress = address
end end
return keyboardId return keyboardAddress
end end
local function onKeyDown(_, address, char, code) local function onKeyDown(_, address, char, code)
if isRunning then return end -- ignore events while running a command 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 if not boundGpu then return end
local x, y = term.getCursor() local x, y = term.getCursor()
local keys = driver.keyboard.keys local keys = driver.keyboard.keys
@ -438,7 +405,7 @@ end
local function onClipboard(_, address, value) local function onClipboard(_, address, value)
if isRunning then return end -- ignore events while running a command 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]+)") value = value:match("([^\r\n]+)")
if value and value:len() > 0 then if value and value:len() > 0 then
command = command .. value command = command .. value
@ -466,13 +433,12 @@ end)
local blinkState = false local blinkState = false
while true do while true do
coroutine.sleep(0.5) coroutine.sleep(0.5)
local gpu = term.gpu() if boundGpu then
if gpu then
local x, y = term.getCursor() local x, y = term.getCursor()
if blinkState then if blinkState then
term.gpu().set(x, y, string.char(0x2588)) -- Solid block. boundGpu.set(x, y, string.char(0x2588)) -- Solid block.
else else
term.gpu().set(x, y, " ") boundGpu.set(x, y, " ")
end end
end end
blinkState = not blinkState blinkState = not blinkState

View File

@ -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 * 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`. * one. All this logic is provided by `Network.joinOrCreateNetwork`.
* <p/> * <p/>
* Note that for network nodes implemented in <tt>TileEntities</tt> 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 * 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 * placed or broken you will have to implement this logic yourself (i.e. call
* <tt>Network.joinOrCreateNetwork</tt> in <tt>onBlockAdded</tt> and * `Network.joinOrCreateNetwork` in `onBlockAdded` and `Network.remove` in
* <tt>Network.remove</tt> in <tt>breakBlock</tt>. * `breakBlock`.
* <p/> * <p/>
* All other kinds of nodes have to be managed manually. See `Node`. * All other kinds of nodes have to be managed manually. See `Node`.
* <p/> * <p/>
* There are a couple of system messages to be aware of. These are all sent by * There are a couple of system messages to be aware of. These are all sent by
* the network manager itself: * the network manager itself:
* <ul> * <ul>
* <li><tt>network.connect</tt> is generated when a node is added to the * <li>`network.connect` is generated when a node is added to the network,
* network, with the added node as the sender.</li> * with the added node as the sender. This will also be sent to the nodes of
* <li><tt>network.disconnect</tt> is generated when a node is removed from the * the other network, when a network merges with another one (both ways).</li>
* network, with the removed node as the sender.</li> * <li>`network.disconnect` is generated when a node is removed from the
* <li><tt>network.reconnect</tt> is generated when a node's address changes, * network, with the removed node as the sender. This will also be sent to the
* usually due to a network merge, with the node whose address changed as the * nodes of the other network(s), when a network is split (all pairs).</li>
* sender and the old address as the only parameter.</li>
* </ul> * </ul>
* <p/> * <p/>
* IMPORTANT: do <em>not</em> implement this interface yourself and create * IMPORTANT: do <em>not</em> 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 * 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 * 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, 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 * nodes is not in this network but in another network, the networks will be
* merged. * merged.
* <p/> * <p/>
@ -66,26 +65,10 @@ trait Network {
*/ */
def connect(nodeA: Node, nodeB: Node): Boolean def connect(nodeA: Node, nodeB: Node): Boolean
/**
* Changes the address of a node.
* <p/>
* 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.
* <p/>
* 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. * Removes a node connection in the network.
* <p/> * <p/>
* Both nodes must be part of the same network. * Both nodes must be part of this network.
* <p/> * <p/>
* This can be useful for cutting connections that depend on some condition * 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 * 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. * Removes a node from the network.
* <p/> * <p/>
* This should be called by nodes when they are destroyed (e.g. onBreakBlock) * This should be called by nodes when they are destroyed (e.g. `breakBlock`)
* or unloaded. If removing the node leads to two graphs (it was the a bridge * or unloaded. Removing the node can lead to one or more new networks if it
* node) the network will be split up. * was the a bridge node, i.e. the only node connecting the resulting
* networks.
* *
* @param node the node to remove from the network. * @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 def remove(node: Node): Boolean
// ----------------------------------------------------------------------- // // ----------------------------------------------------------------------- //
/** /**
* Get the valid network node with the specified address. * Get the 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.
* *
* @param address the address of the node to get. * @param address the address of the node to get.
* @return the node with that address. * @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.
* <p/> * <p/>
* This does not include nodes with an address less or equal to zero or with * This does *not* include nodes with a visibility of `Visibility.None`.
* a visibility of `Visibility.None`.
* *
* @return the list of nodes in this network. * @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 list of nodes in the network visible to the specified node.
* <p/> * <p/>
* The same base filters as for `nodes` apply, with additional visibility * This does *not* include nodes with a visibility of `Visibility.None` or
* checks applied, based on the specified node as a point of reference. * 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`.
* <p/> * <p/>
* This can be used to perform a delayed initialization of a node. For * This can be useful when performing a delayed initialization of a node.
* example, computers will use this when starting up to generate component * For example, computers will use this when starting up to generate
* added events for all nodes. * `component_added` signals for all visible nodes in the network.
* *
* @param reference the node to get the visible other nodes for. * @param reference the node to get the visible other nodes for.
* @return the nodes visible to the specified node. * @return the nodes visible to the specified node.
@ -153,10 +133,9 @@ trait Network {
def nodes(reference: Node): Iterable[Node] 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.
* <p/> * <p/>
* This does not include nodes with an address less or equal to zero or with * This does *not* include nodes with a visibility of `Visibility.None`.
* a visibility of `Visibility.None`.
* <p/> * <p/>
* This can be used to verify arguments for components that should only work * This can be used to verify arguments for components that should only work
* for other components that are directly connected to them, for example. * 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.
* <p/> * <p/>
* If the target is less or equal to zero no message is sent. If a node with * If the target node with that address has a visibility of `Visibility.None`
* that address has a visibility of `Visibility.None` the message will not be * 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. * delivered to that node.
* <p/> * <p/>
* Messages should have a unique name to allow differentiating them when * Messages should have a unique name to allow differentiating them when
* handling them in a network node. For example, computers will try to parse * handling them in a network node. For example, computers will try to parse
* messages named "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 * 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`.
* <p/> * <p/>
* Note that message handlers may also return results. In this case that * Note that message handlers may also return results. In this case that
* result will be returned from this function. In the case that there are * result will be returned from this function. In the case that there are
@ -193,53 +174,52 @@ trait Network {
* @param name the name of the message. * @param name the name of the message.
* @param data the message to send. * @param data the message to send.
* @return the result of the message being handled, if any. * @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.
* <p/> * <p/>
* This does not include nodes with an address less or equal to zero or with * Targets are determined using `neighbors`.
* a visibility of `Visibility.None`.
* <p/> * <p/>
* Messages should have a unique name to allow differentiating them when * Messages should have a unique name to allow differentiating them when
* handling them in a network node. For example, computers will try to parse * handling them in a network node. For example, computers will try to parse
* messages named "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 * 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 source the node that sends the message.
* @param name the name of the message. * @param name the name of the message.
* @param data the message to send. * @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*) 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.
* <p/> * <p/>
* This does not include nodes with an address less or equal to zero or with * Targets are determined using `nodes(source)`.
* 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/> * <p/>
* Messages should have a unique name to allow differentiating them when * Messages should have a unique name to allow differentiating them when
* handling them in a network node. For example, computers will try to parse * handling them in a network node. For example, computers will try to parse
* messages named "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 * 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 source the node that sends the message.
* @param data the message to send. * @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 { 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 world the world the tile entity lives in.
* @param x the X coordinate of the tile entity. * @param x the X coordinate of the tile entity.

View File

@ -75,6 +75,8 @@ trait Item extends Driver {
* @return the tag to use for saving and loading. * @return the tag to use for saving and loading.
*/ */
def nbt(item: ItemStack) = { def nbt(item: ItemStack) = {
if (!item.hasTagCompound)
item.setTagCompound(new NBTTagCompound())
val nbt = item.getTagCompound val nbt = item.getTagCompound
if (!nbt.hasKey("oc.node")) { if (!nbt.hasKey("oc.node")) {
nbt.setCompoundTag("oc.node", new NBTTagCompound()) nbt.setCompoundTag("oc.node", new NBTTagCompound())

View File

@ -1,16 +1,14 @@
package li.cil.oc.api.network package li.cil.oc.api.network
import li.cil.oc.api.{Persistable, 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`. * A single node in a `INetwork`.
* <p/> * <p/>
* All nodes in a network <em>should</em> have a unique address; the network * All nodes in a network <em>must</em> have a unique address; the network will
* will try to generate a unique address and assign it to new nodes. A node must * generate a unique address and assign it to new nodes. A node must never ever
* never ever change its address while in a network (because the lookup-table in * change its address on its own accord.
* the network manager would not be notified of this change). If you must change
* the address, use `Network.reconnect`.
* <p/> * <p/>
* Per default there are two kinds of nodes: tile entities and item components. * Per default there are two kinds of nodes: tile entities and item components.
* If a `TileEntity` implements this interface adding/removal from its * 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 * All other kinds of nodes you may come up with will also have to be handled
* manually. * manually.
* <p/> * <p/>
* Items have to be handled by a corresponding `IItemDriver`. Existing * Items have to be handled by a corresponding `ItemDriver`. Existing
* blocks may be interfaced with a proxy block if a `IBlockDriver` exists * blocks may be interfaced with a proxy block if a `BlockDriver` exists
* that supports the block. * that supports the block.
*/ */
trait Node extends Persistable { 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. * The address of the node, so that it can be found in the network.
* <p/> * <p/>
* This is used by the network manager when a node is added to a network to * 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 * assign it a unique address, if it doesn't already have one. Nodes must not
* addresses since that could lead to multiple nodes with the same address in * use custom addresses, only those assigned by the network. The only option
* a network. Note that that in and by itself is supported however, it is just * they have is to *not* have an address, which can be useful for "dummy"
* harder to work with. * nodes, such as cables. In that case they may ignore the address being set.
* <p/>
* 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).
* *
* @return the id of this node. * @return the id of this node.
*/ */
var address = 0 var address: Option[String] = None
/** /**
* The network this node is currently in. * The network this node is currently in.
* <p/> * <p/>
* 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 * 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.
* <p/> * <p/>
* This is used by the `NetworkAPI` and the network itself when merging with * This will always be set automatically by the network manager. Do not
* another network. You should never have to set this yourself. * change this value and do not return anything that it wasn't set to.
* *
* @return the network the node is in. * @return the network the node is in.
*/ */
@ -101,7 +95,6 @@ trait Node extends Persistable {
if (message.source == this) message.name match { if (message.source == this) message.name match {
case "network.connect" => onConnect() case "network.connect" => onConnect()
case "network.disconnect" => onDisconnect() case "network.disconnect" => onDisconnect()
case "network.reconnect" => onReconnect()
case _ => // Ignore. case _ => // Ignore.
} }
None None
@ -109,31 +102,44 @@ trait Node extends Persistable {
/** /**
* Reads a previously stored address value from the specified tag. * Reads a previously stored address value from the specified tag.
* * <p/>
* 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. * @param nbt the tag to read from.
*/ */
def load(nbt: NBTTagCompound) = { def load(nbt: NBTTagCompound) = {
network match { if (nbt.hasKey("address") && nbt.getTag("address").isInstanceOf[NBTTagString])
case None => address = nbt.getInteger("address") address = Option(nbt.getString("address"))
case Some(net) => net.reconnect(this, nbt.getInteger("address"))
}
} }
/** /**
* Stores the node's address in the specified NBT tag, to keep addresses the * Stores the node's address in the specified NBT tag, to keep addresses the
* same across unloading/loading. * same across unloading/loading.
* * <p/>
* 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. * @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.
* <p/>
* Use this for custom initialization logic.
*/
protected def onConnect() {} protected def onConnect() {}
/**
* Called when this node is removed from a network.
* <p/>
* 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 onDisconnect() {}
protected def onReconnect() {}
} }

View File

@ -15,7 +15,7 @@ trait ScreenEnvironment extends Node {
override def name = "screen" override def name = "screen"
override def visibility = Visibility.Neighbors override def visibility = Visibility.Network
override def receive(message: Message): Option[Array[Any]] = { override def receive(message: Message): Option[Array[Any]] = {
super.receive(message) super.receive(message)
@ -53,7 +53,7 @@ trait ScreenEnvironment extends Node {
} }
def onScreenResolutionChange(w: Int, h: Int) = 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) {} def onScreenSet(col: Int, row: Int, s: String) {}

View File

@ -32,14 +32,14 @@ class Computer(isClient: Boolean) extends Rotatable with ComputerEnvironment wit
message.data match { message.data match {
// The isRunning check is here to avoid component_* signals being // The isRunning check is here to avoid component_* signals being
// generated while loading a chunk. // generated while loading a chunk.
case Array() if message.name == "network.connect" && message.source.address != 0 && isRunning => case Array() if message.name == "network.connect" && isRunning =>
computer.signal("component_added", message.source.address); None computer.signal("component_added", message.source.address.get); None
case Array() if message.name == "network.disconnect" && message.source.address != 0 && isRunning => case Array() if message.name == "network.disconnect" && isRunning =>
computer.signal("component_removed", message.source.address); None computer.signal("component_removed", message.source.address.get); None
case Array(oldAddress: Integer) if message.name == "network.reconnect" && isRunning => case Array(oldAddress: Integer) if message.name == "network.reconnect" && isRunning =>
computer.signal("component_changed", message.source.address, oldAddress); None computer.signal("component_changed", message.source.address.get, oldAddress); None
case Array(name: String, args@_*) if message.name == "computer.signal" => 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" => case Array() if message.name == "computer.start" =>
Some(Array(turnOn().asInstanceOf[Any])) Some(Array(turnOn().asInstanceOf[Any]))
case Array() if message.name == "computer.stop" => 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 // General
// ----------------------------------------------------------------------- // // ----------------------------------------------------------------------- //
@ -97,9 +92,9 @@ class Computer(isClient: Boolean) extends Rotatable with ComputerEnvironment wit
if (isRunning != computer.isRunning) { if (isRunning != computer.isRunning) {
isRunning = computer.isRunning isRunning = computer.isRunning
if (isRunning) if (isRunning)
network.foreach(_.sendToAll(this, "computer.started")) network.foreach(_.sendToVisible(this, "computer.started"))
else else
network.foreach(_.sendToAll(this, "computer.stopped")) network.foreach(_.sendToVisible(this, "computer.stopped"))
ServerPacketSender.sendComputerState(this, isRunning) ServerPacketSender.sendComputerState(this, isRunning)
} }
} }

View File

@ -15,13 +15,13 @@ class Keyboard extends Rotatable with Node {
message.data match { message.data match {
case Array(p: Player, char: Char, code: Int) if message.name == "keyboard.keyDown" => case Array(p: Player, char: Char, code: Int) if message.name == "keyboard.keyDown" =>
if (isUseableByPlayer(p)) 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" => case Array(p: Player, char: Char, code: Int) if message.name == "keyboard.keyUp" =>
if (isUseableByPlayer(p)) 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" => case Array(p: Player, value: String) if message.name == "keyboard.clipboard" =>
if (isUseableByPlayer(p)) if (isUseableByPlayer(p))
network.foreach(_.sendToAll(this, "computer.signal", "clipboard", value)) network.foreach(_.sendToVisible(this, "computer.signal", "clipboard", value))
case _ => // Ignore. case _ => // Ignore.
} }
None None

View File

@ -159,7 +159,7 @@ class Computer(val owner: Computer.Environment) extends component.Computer with
signal("") signal("")
// Inject component added signals for all nodes in the network. // 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. // All green, computer started successfully.
true 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 // exception to a string (which we have to do to avoid keeping
// userdata on the stack, which cannot be persisted). // 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 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() close()
case e: java.lang.Error if e.getMessage == "not enough memory" => 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 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. // 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() close()
case e: Throwable => { case e: Throwable => {
OpenComputers.log.log(Level.WARNING, "Faulty Lua implementation for synchronized calls.", e) 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. // Allow the computer to figure out its own id in the component network.
lua.pushScalaFunction(lua => { lua.pushScalaFunction(lua => {
lua.pushInteger(owner.address) owner.address match {
case None => lua.pushNil()
case Some(address) => lua.pushString(address)
}
1 1
}) })
lua.setField(-2, "address") lua.setField(-2, "address")
@ -555,7 +558,7 @@ class Computer(val owner: Computer.Environment) extends component.Computer with
lua.pushScalaFunction(lua => lua.pushScalaFunction(lua =>
owner.network.fold(None: Option[Array[Any]])(_. 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@_*)) => case Some(Array(results@_*)) =>
results.foreach(pushResult(lua, _)) results.foreach(pushResult(lua, _))
results.length results.length
@ -563,14 +566,14 @@ class Computer(val owner: Computer.Environment) extends component.Computer with
}) })
lua.setGlobal("sendToNode") lua.setGlobal("sendToNode")
lua.pushScalaFunction(lua => { // lua.pushScalaFunction(lua => {
owner.network.foreach(_.sendToAll(owner, lua.checkString(1), parseArguments(lua, 2): _*)) // owner.network.foreach(_.sendToVisible(owner, lua.checkString(1), parseArguments(lua, 2): _*))
0 // 0
}) // })
lua.setGlobal("sendToAll") // lua.setGlobal("sendToAll")
lua.pushScalaFunction(lua => { 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 None => 0
case Some(node) => lua.pushString(node.name); 1 case Some(node) => lua.pushString(node.name); 1
} }

View File

@ -10,24 +10,19 @@ import scala.collection.mutable
class FileSystem(val fileSystem: api.FileSystem) extends ItemComponent { 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 name = "fs"
override def receive(message: Message) = { override def receive(message: Message) = {
message.data match { message.data match {
case Array() if message.name == "network.disconnect" && handles.contains(message.source.address) => case Array() if message.name == "network.disconnect" && handles.contains(message.source.address.get) =>
for (handle <- handles(message.source.address)) { for (handle <- handles(message.source.address.get)) {
fileSystem.file(handle) match { fileSystem.file(handle) match {
case None => // Maybe file system was accessed from somewhere else. case None => // Maybe file system was accessed from somewhere else.
case Some(file) => file.close() 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. case _ => // Ignore.
} }
super.receive(message) 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" => 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"))) val handle = fileSystem.open(clean(path), Mode.parse(new String(mode, "UTF-8")))
if (handle > 0) { 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])) Some(Array(handle.asInstanceOf[Any]))
@ -62,7 +57,7 @@ class FileSystem(val fileSystem: api.FileSystem) extends ItemComponent {
fileSystem.file(handle.toLong) match { fileSystem.file(handle.toLong) match {
case None => // Ignore. case None => // Ignore.
case Some(file) => 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 None => // Not the owner of this handle.
case Some(set) => if (set.remove(handle.toLong)) file.close() case Some(set) => if (set.remove(handle.toLong)) file.close()
} }

View File

@ -9,29 +9,29 @@ class GraphicsCard extends ItemComponent {
override def name = "gpu" override def name = "gpu"
override protected def receiveFromNeighbor(network: Network, message: Message) = message.data match { 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))) 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 else
Some(Array(Unit, "unsupported resolution")) Some(Array(Unit, "unsupported resolution"))
case Array(screen: Double) if message.name == "gpu.resolution" => case Array(screen: Array[Byte]) if message.name == "gpu.resolution" =>
network.sendToAddress(this, screen.toInt, "screen.resolution") network.sendToAddress(this, new String(screen, "UTF-8"), "screen.resolution")
case Array(screen: Double) if message.name == "gpu.resolutions" => case Array(screen: Array[Byte]) if message.name == "gpu.resolutions" =>
network.sendToAddress(this, screen.toInt, "screen.resolutions") match { network.sendToAddress(this, new String(screen, "UTF-8"), "screen.resolutions") match {
case Some(Array(resolutions@_*)) => case Some(Array(resolutions@_*)) =>
Some(Array(supportedResolutions.intersect(resolutions): _*)) Some(Array(supportedResolutions.intersect(resolutions): _*))
case _ => None case _ => None
} }
case Array(screen: Double, x: Double, y: Double, value: Array[Byte]) if message.name == "gpu.set" => case Array(screen: Array[Byte], 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")) network.sendToAddress(this, new String(screen, "UTF-8"), "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, w: Double, h: Double, value: Array[Byte]) if message.name == "gpu.fill" =>
val s = new String(value, "UTF-8") val s = new String(value, "UTF-8")
if (s.length == 1) 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 else
Some(Array(Unit, "invalid fill value")) 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" => 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, screen.toInt, "screen.copy", x.toInt - 1, y.toInt - 1, w.toInt, h.toInt, tx.toInt, ty.toInt) 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 case _ => None
} }
} }

View File

@ -10,18 +10,18 @@ class RedstoneCard extends ItemComponent {
override def name = "redstone" override def name = "redstone"
override protected def receiveFromNeighbor(network: Network, message: Message) = message.data match { override protected def receiveFromNeighbor(network: Network, message: Message) = message.data match {
case Array(target: Double, side: Double) if message.name == "redstone.input" => case Array(target: Array[Byte], side: Double) if message.name == "redstone.input" =>
input(target.toInt, side.toInt) input(new String(target, "UTF-8"), side.toInt)
case Array(target: Double, side: Double) if message.name == "redstone.output" => case Array(target: Array[Byte], side: Double) if message.name == "redstone.output" =>
output(target.toInt, side.toInt) output(new String(target, "UTF-8"), side.toInt)
case Array(target: Double, side: Double, value: Double) if message.name == "redstone.output=" => case Array(target: Array[Byte], side: Double, value: Double) if message.name == "redstone.output=" =>
output(target.toInt, side.toInt, value.toInt); None output(new String(target, "UTF-8"), side.toInt, value.toInt); None
case _ => None // Ignore. 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(r: RedstoneEnabled) => Some(Array(r.input(ForgeDirection.getOrientation(side)).asInstanceOf[Any]))
case Some(t: TileEntity) => case Some(t: TileEntity) =>
val face = ForgeDirection.getOrientation(side.toInt) val face = ForgeDirection.getOrientation(side.toInt)
@ -31,7 +31,7 @@ class RedstoneCard extends ItemComponent {
case _ => None // Can't work with this node. case _ => None // Can't work with this node.
} else None } 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(r: RedstoneEnabled) => Some(Array(r.output(ForgeDirection.getOrientation(side)).asInstanceOf[Any]))
case Some(t: TileEntity) => case Some(t: TileEntity) =>
val power = t.worldObj.isBlockProvidingPowerTo(t.xCoord, t.yCoord, t.zCoord, side.toInt) 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. case _ => None // Can't work with this node.
} else None } 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 Some(r: RedstoneEnabled) => r.output(ForgeDirection.getOrientation(side)) = value
case _ => // Can't work with this node. case _ => // Can't work with this node.
} }

View File

@ -1,52 +1,39 @@
package li.cil.oc.server.network 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 java.util.logging.Level
import li.cil.oc.api.network.Visibility 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 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.beans.BeanProperty
import scala.collection.JavaConverters._ import scala.collection.JavaConverters._
import scala.collection.mutable import scala.collection.mutable
import scala.collection.mutable.ArrayBuffer import scala.collection.mutable.ArrayBuffer
/** class Network private(private val addressedNodes: mutable.Map[String, Network.Node] = mutable.Map.empty,
* Network implementation for component networks. private val unaddressedNodes: mutable.ArrayBuffer[Network.Node] = mutable.ArrayBuffer.empty) extends api.Network {
* def this(node: api.network.Node) = {
* This network interconnects components in a geometry-agnostic fashion. It this()
* builds an internal graph of network nodes and the connections between them, addNew(node)
* and takes care of merges when adding connections, as well as net splits on send(Network.ConnectMessage(node), Iterable(node))
* 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))
} }
nodes.foreach(_.network = Some(this)) nodes.foreach(_.network = Some(this))
def connect(nodeA: net.Node, nodeB: net.Node) = { def connect(nodeA: api.network.Node, nodeB: api.network.Node) = {
val containsA = nodeMap.get(nodeA.address).exists(_.exists(_.data == nodeA)) val containsA = contains(nodeA)
val containsB = nodeMap.get(nodeB.address).exists(_.exists(_.data == nodeB)) val containsB = contains(nodeB)
if (!containsA && !containsB) throw new IllegalArgumentException( if (!containsA && !containsB) throw new IllegalArgumentException(
"At least one of the nodes must already be in this network.") "At least one of the nodes must already be in this network.")
def oldNodeA = nodeMap(nodeA.address).find(_.data == nodeA).get def oldNodeA = node(nodeA)
def oldNodeB = nodeMap(nodeB.address).find(_.data == nodeB).get def oldNodeB = node(nodeB)
if (containsA && containsB) { if (containsA && containsB) {
// Both nodes already exist in the network but there is a new connection. // 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 // 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) Network.Edge(oldNodeA, oldNodeB)
true true
} }
// That connection already exists. else false // That connection already exists.
else false
} }
// New node for this network, order the nodes and add the new one.
else if (containsA) add(oldNodeA, nodeB) else if (containsA) add(oldNodeA, nodeB)
else add(oldNodeB, nodeA) 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. // Check if the other node is new or if we have to merge networks.
val (newNode, sendQueue) = if (addedNode.network.isEmpty) { val sendQueue = mutable.Buffer.empty[(Network.Message, Iterable[api.network.Node])]
val newNode = new Network.Node(addedNode) val (newNode) = if (addedNode.network.isEmpty) {
if (nodeMap.contains(addedNode.address) || addedNode.address < 1) val newNode = addNew(addedNode)
addedNode.address = findId() if (addedNode.address.isDefined) addedNode.visibility match {
// Store everything with an invalid address in slot zero. case Visibility.None => // Nothing to do.
val address = addedNode.address match { case Visibility.Neighbors =>
case a if a > 0 => a sendQueue += ((Network.ConnectMessage(addedNode), Iterable(addedNode) ++ neighbors(addedNode)))
case _ => 0 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, newNode
// 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)
} }
else { else {
// Queue any messages to avoid side effects from receivers. // Queue any messages to avoid side effects from receivers.
val sendQueue = mutable.Buffer.empty[(Network.Message, Iterable[net.Node])]
val thisNodes = nodes.toBuffer val thisNodes = nodes.toBuffer
val otherNetwork = addedNode.network.get.asInstanceOf[Network] val otherNetwork = addedNode.network.get.asInstanceOf[Network]
val otherNodes = otherNetwork.nodes.toBuffer val otherNodes = otherNetwork.nodes.toBuffer
otherNodes.foreach(node => sendQueue += ((Network.ConnectMessage(node), thisNodes))) otherNodes.foreach(node => sendQueue += ((Network.ConnectMessage(node), thisNodes)))
thisNodes.foreach(node => sendQueue += ((Network.ConnectMessage(node), otherNodes))) thisNodes.foreach(node => sendQueue += ((Network.ConnectMessage(node), otherNodes)))
// Change addresses for conflicting nodes in other network. We can queue // Add nodes from other network into this network.
// these messages because we're storing references to the nodes, so they otherNetwork.addressedNodes.values.foreach(node => {
// will send the change notification to the right node even if that node addressedNodes += node.data.address.get -> 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
node.data.network = Some(this) 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. // 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. // Add the connection between the two nodes.
@ -131,195 +219,66 @@ class Network private(private val nodeMap: mutable.Map[Int, ArrayBuffer[Network.
true true
} }
def reconnect(node: net.Node, address: Int): Boolean = { private def handleSplit(subGraphs: Seq[(mutable.Map[String, Network.Node], mutable.ArrayBuffer[Network.Node])]) =
if (!nodeMap.get(node.address).exists(_.exists(_.data == node))) throw new IllegalArgumentException( if (subGraphs.size > 1) {
"The node must already be in this network.") val subNodes = subGraphs.map {
case (addressed, unaddressed) =>
val oldAddress = node.address (addressed.values ++ unaddressed).map(_.data).filter(_.visibility != Visibility.None)
if (address == oldAddress) false }
else { val visibleNodes = subGraphs.map {
val otherMessage = if (address < 1) { case (addressed, unaddressed) =>
node.address = 0 addressed.values.map(_.data).filter(_.visibility == Visibility.Network)
None
} }
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() addressedNodes.clear()
nodeMap.getOrElseUpdate(other.data.address, new mutable.ArrayBuffer[Node]) += other unaddressedNodes.clear()
Some((Network.ReconnectMessage(other.data, address), nodes))
} 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 private def send(message: Network.Message, targets: Iterable[api.network.Node]) = {
// onDisconnect handler), then one for the removed node to all sub def protectedSend(target: api.network.Node) = try {
// networks, for each node in the sub networks back to the removed node //println("receive(" + message.name + "(" + message.data.mkString(", ") + "): " + message.source.address.get + ":" + message.source.name + " -> " + target.address.get + ":" + target.name + ")")
// and if there was a net split (we have multiple networks) also for target.receive(message)
// each node now longer belonging to one of the resulting sub networks. } catch {
for (indexA <- 0 until subNodes.length) { case e: Throwable => OpenComputers.log.log(Level.WARNING, "Error in message handler", e); None
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))
} }
def sendToNeighbors(source: net.Node, name: String, data: Any*) = message match {
send(new Network.Message(source, name, Array(data: _*)), neighbors(source)) case _@(Network.ConnectMessage(_) | Network.DisconnectMessage(_)) =>
targets.foreach(protectedSend)
def sendToAll(source: net.Node, name: String, data: Any*) = None
send(new Network.Message(source, name, Array(data: _*)), nodes) case _ =>
var result = None: Option[Array[Any]]
private def send(message: Network.Message, targets: Iterable[net.Node]) = val iterator = targets.iterator
if (message.source.address > 0 && message.source.visibility != Visibility.None) { while (!message.isCanceled && iterator.hasNext)
def protectedSend(target: net.Node) = try { protectedSend(iterator.next()) match {
//println("receive(" + message.name + "(" + message.data.mkString(", ") + "): " + message.source.address + ":" + message.source.name + " -> " + target.address + ":" + target.name + ")") case None => // Ignore.
target.receive(message) case r => result = r
} catch { }
case e: Throwable => OpenComputers.log.log(Level.WARNING, "Error in message handler", e); None result
} }
}
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
} }
object Network extends api.detail.NetworkAPI { 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) { 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? // TODO add a more efficient batch remove operation? something along the lines of if #remove > #nodes*factor remove all, re-add remaining?
tileEntities. tileEntities.
filter(_.isInstanceOf[net.Node]). filter(_.isInstanceOf[api.network.Node]).
map(_.asInstanceOf[net.Node]). map(_.asInstanceOf[api.network.Node]).
foreach(t => t.network.foreach(_.remove(t))) 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 { Option(Block.blocksList(world.getBlockId(x, y, z))) match {
case Some(block) if block.hasTileEntity(world.getBlockMetadata(x, y, z)) => case Some(block) if block.hasTileEntity(world.getBlockMetadata(x, y, z)) =>
world.getBlockTileEntity(x, y, z) match { 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
} }
case _ => None case _ => None
} }
private class Node(val data: net.Node) { private class Node(val data: api.network.Node) {
val edges = ArrayBuffer.empty[Edge] val edges = ArrayBuffer.empty[Edge]
def remove() = { def remove() = {
@ -397,41 +356,35 @@ object Network extends api.detail.NetworkAPI {
private def searchGraphs(seeds: Seq[Node]) = { private def searchGraphs(seeds: Seq[Node]) = {
val seen = mutable.Set.empty[Node] val seen = mutable.Set.empty[Node]
seeds.map(seed => { seeds.map(seed => {
if (seen.contains(seed)) { if (seen.contains(seed)) None
// 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]]
}
else { else {
// Not yet processed, start growing a network from here. We're val addressed = mutable.Map.empty[String, Node]
// guaranteed to not find previously processed nodes, since edges val unaddressed = mutable.ArrayBuffer.empty[Node]
// are bidirectional, and we'd be in the other branch otherwise. val queue = mutable.Queue(seed)
seen += seed
val subGraph = mutable.Map(seed.data.address -> mutable.ArrayBuffer(seed))
val queue = mutable.Queue(seed.edges.map(_.other(seed)): _*)
while (queue.nonEmpty) { while (queue.nonEmpty) {
val node = queue.dequeue() val node = queue.dequeue()
seen += node 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)) 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 name: String,
@BeanProperty val data: Array[Any] = Array()) extends net.Message { @BeanProperty val data: Array[Any] = Array()) extends api.network.Message {
var isCanceled = false var isCanceled = false
def cancel() = isCanceled = true 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 DisconnectMessage(override val source: api.network.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]))
} }