mirror of
https://github.com/MightyPirates/OpenComputers.git
synced 2025-09-10 07:45:05 -04:00
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:
parent
06eac9254c
commit
59febb056f
@ -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
|
@ -1,4 +1,3 @@
|
|||||||
--[[ API for keyboards. ]]
|
|
||||||
driver.keyboard = {}
|
driver.keyboard = {}
|
||||||
|
|
||||||
driver.keyboard.keys = {
|
driver.keyboard.keys = {
|
||||||
|
@ -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"}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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.
|
||||||
|
@ -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())
|
||||||
|
@ -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() {}
|
|
||||||
}
|
}
|
@ -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) {}
|
||||||
|
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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()
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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.
|
||||||
}
|
}
|
||||||
|
@ -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]))
|
|
||||||
|
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user