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