diff --git a/README.md b/README.md index b12ee3b0..35ae9b8d 100644 --- a/README.md +++ b/README.md @@ -134,7 +134,6 @@ Recommended mods that build on the `technic mod`: | quarry_time_limit | max cpu time in μs allowed per quarry step. | | quarry_dig_above_nodes | begin digging this many nodes above quarry node. | | network_overload_reset_time | After network conflict wait this many seconds before attempting to activate conflicting networks again. | -| switch_max_range | max cable length. | | switch_off_delay_seconds | switching station off delay. | See defaults for settings here: [technic/config.lua](https://github.com/mt-mods/technic/blob/master/technic/config.lua) @@ -143,6 +142,8 @@ See defaults for settings here: [technic/config.lua](https://github.com/mt-mods/ * **/technic_flush_switch_cache** clears the switching station cache (stops all unloaded switches) * **/powerctrl [on|off]** enable/disable technic power distribution globally +* **/technic_get_active_networks [minlag]** list all active networks with additional network data +* **/technic_clear_network_data** removes all networks and network nodes from the cache # Contributors diff --git a/technic/config.lua b/technic/config.lua index 658bbcfc..7c8a5099 100644 --- a/technic/config.lua +++ b/technic/config.lua @@ -14,7 +14,6 @@ local defaults = { enable_entity_radiation_damage = "true", enable_longterm_radiation_damage = "true", enable_nuclear_reactor_digiline_selfdestruct = "false", - switch_max_range = "256", switch_off_delay_seconds = "1800", network_overload_reset_time = "20", admin_priv = "basic_privs", diff --git a/technic/machines/network.lua b/technic/machines/network.lua index f6df4458..20127be2 100644 --- a/technic/machines/network.lua +++ b/technic/machines/network.lua @@ -3,9 +3,10 @@ -- local S = technic.getter -local switch_max_range = tonumber(technic.config:get("switch_max_range")) local off_delay_seconds = tonumber(technic.config:get("switch_off_delay_seconds")) +local network_node_arrays = {"PR_nodes","BA_nodes","RE_nodes"} + technic.active_networks = {} local networks = {} technic.networks = networks @@ -21,6 +22,52 @@ function technic.create_network(sw_pos) return network_id end +local function pos_in_array(pos, array) + for _,pos2 in ipairs(array) do + if pos.x == pos2.x and pos.y == pos2.y and pos.z == pos2.z then + return true + end + end + return false +end + +function technic.merge_networks(net1, net2) + -- TODO: Optimize for merging small network into larger by first checking network + -- node counts for both networks and keep network id with most nodes. + assert(type(net1) == "table", "Invalid net1 for technic.merge_networks") + assert(type(net2) == "table", "Invalid net2 for technic.merge_networks") + assert(net1 ~= net2, "Deadlock recipe: net1 & net2 equals for technic.merge_networks") + -- Move data in cables table + for node_id,cable_net_id in pairs(cables) do + if cable_net_id == net2.id then + cables[node_id] = net1.id + end + end + -- Move data in machine tables + for _,tablename in ipairs(network_node_arrays) do + for _,pos in ipairs(net2[tablename]) do + table.insert(net1[tablename], pos) + end + end + -- Move data in all_nodes table + for node_id,pos in pairs(net2.all_nodes) do + net1.all_nodes[node_id] = pos + end + -- Merge queues for incomplete networks + if net1.queue and net2.queue then + for _,pos in ipairs(net2.queue) do + if not pos_in_array(pos, net1.queue) then + table.insert(net1.queue, pos) + end + end + else + net1.queue = net1.queue or net2.queue + end + -- Remove links to net2 + networks[net2.id] = nil + technic.active_networks[net2.id] = nil +end + function technic.activate_network(network_id, timeout) -- timeout is optional ttl for network in seconds, if not specified use default local network = networks[network_id] @@ -53,7 +100,6 @@ function technic.remove_network(network_id) end -- Remove machine or cable from network -local network_node_arrays = {"PR_nodes","BA_nodes","RE_nodes"} function technic.remove_network_node(network_id, pos) local network = networks[network_id] if not network then return end @@ -109,11 +155,18 @@ function technic.network2sw_pos(network_id) end function technic.network_infotext(network_id, text) - if networks[network_id] == nil then return end - if text then - networks[network_id].infotext = text - else - return networks[network_id].infotext + local network = networks[network_id] + if network then + if text then + network.infotext = text + elseif network.queue then + local count = #network.PR_nodes + + #network.RE_nodes + + #network.BA_nodes + return S("Building Network: %d Nodes"):format(count) + else + return network.infotext + end end end @@ -217,54 +270,52 @@ local function add_network_machine(nodes, pos, network_id, all_nodes, multitier) end -- Add a wire node to the LV/MV/HV network -local function add_cable_node(nodes, pos, network_id, queue) +local function add_cable_node(pos, network) local node_id = poshash(pos) if not cables[node_id] then - cables[node_id] = network_id - nodes[node_id] = pos - table.insert(queue, pos) + cables[node_id] = network.id + network.all_nodes[node_id] = pos + if network.queue then + table.insert(network.queue, pos) + end + elseif cables[node_id] ~= network.id then + -- Conflicting network connected, merge networks if both are still in building stage + local net2 = networks[cables[node_id]] + if net2 and net2.queue then + technic.merge_networks(network, net2) + end end end -- Generic function to add found connected nodes to the right classification array -local function add_network_node(PR_nodes, RE_nodes, BA_nodes, all_nodes, pos, machines, tier, network_id, queue) +local function add_network_node(network, pos, machines) technic.get_or_load_node(pos) local name = minetest.get_node(pos).name - if technic.is_tier_cable(name, tier) then - add_cable_node(all_nodes, pos, network_id, queue) + if technic.is_tier_cable(name, network.tier) then + add_cable_node(pos, network) elseif machines[name] then if machines[name] == technic.producer then - add_network_machine(PR_nodes, pos, network_id, all_nodes) + add_network_machine(network.PR_nodes, pos, network.id, network.all_nodes) elseif machines[name] == technic.receiver then - add_network_machine(RE_nodes, pos, network_id, all_nodes) + add_network_machine(network.RE_nodes, pos, network.id, network.all_nodes) elseif machines[name] == technic.producer_receiver then - if add_network_machine(PR_nodes, pos, network_id, all_nodes, true) then - table.insert(RE_nodes, pos) + if add_network_machine(network.PR_nodes, pos, network.id, network.all_nodes, true) then + table.insert(network.RE_nodes, pos) end elseif machines[name] == technic.battery then - add_network_machine(BA_nodes, pos, network_id, all_nodes) + add_network_machine(network.BA_nodes, pos, network.id, network.all_nodes) end end end -- Generic function to add single nodes to the right classification array of existing network function technic.add_network_node(pos, network) - add_network_node( - network.PR_nodes, - network.RE_nodes, - network.BA_nodes, - network.all_nodes, - pos, - technic.machines[network.tier], - network.tier, - network.id, - {} - ) + add_network_node(network, pos, technic.machines[network.tier]) end -- Traverse a network given a list of machines and a cable type name -local function traverse_network(PR_nodes, RE_nodes, BA_nodes, all_nodes, pos, machines, tier, network_id, queue) +local function traverse_network(network, pos, machines) local positions = { {x=pos.x+1, y=pos.y, z=pos.z}, {x=pos.x-1, y=pos.y, z=pos.z}, @@ -273,8 +324,8 @@ local function traverse_network(PR_nodes, RE_nodes, BA_nodes, all_nodes, pos, ma {x=pos.x, y=pos.y, z=pos.z+1}, {x=pos.x, y=pos.y, z=pos.z-1}} for i, cur_pos in pairs(positions) do - if not all_nodes[poshash(cur_pos)] then - add_network_node(PR_nodes, RE_nodes, BA_nodes, all_nodes, cur_pos, machines, tier, network_id, queue) + if not network.all_nodes[poshash(cur_pos)] then + add_network_node(network, cur_pos, machines) end end end @@ -287,7 +338,7 @@ end local function get_network(network_id, tier) local cached = networks[network_id] - if cached and cached.tier == tier then + if cached and not cached.queue and cached.tier == tier then touch_nodes(cached.PR_nodes, tier) touch_nodes(cached.BA_nodes, tier) touch_nodes(cached.RE_nodes, tier) @@ -298,27 +349,23 @@ end function technic.add_network_branch(queue, network) -- Adds whole branch to network, queue positions can be used to bypass sub branches - local PR_nodes = network.PR_nodes -- Indexed array - local BA_nodes = network.BA_nodes -- Indexed array - local RE_nodes = network.RE_nodes -- Indexed array - local all_nodes = network.all_nodes -- Hash table - local network_id = network.id - local tier = network.tier - local machines = technic.machines[tier] - local sw_pos = technic.network2sw_pos(network_id) + local machines = technic.machines[network.tier] --print(string.format("technic.add_network_branch(%s, %s, %.17g)",queue,minetest.pos_to_string(sw_pos),network.id)) + local t1 = minetest.get_us_time() while next(queue) do local to_visit = {} for _, pos in ipairs(queue) do - if vector.distance(pos, sw_pos) > switch_max_range then - -- max range exceeded - return - end - traverse_network(PR_nodes, RE_nodes, BA_nodes, all_nodes, pos, - machines, tier, network_id, to_visit) + network.queue = to_visit + traverse_network(network, pos, machines) end queue = to_visit + if minetest.get_us_time() - t1 > 10000 then + -- time limit exceeded + break + end end + -- Set build queue for network if network build was not finished within time limits + network.queue = #queue > 0 and queue end -- Battery charge status updates for network @@ -348,34 +395,46 @@ local function sma(period) end function technic.build_network(network_id) - technic.remove_network(network_id) - local sw_pos = technic.network2sw_pos(network_id) - local tier = technic.sw_pos2tier(sw_pos) - if not tier then - return - end - local network = { - -- Basic network data and lookup table for attached nodes (no switching stations) - id = network_id, tier = tier, all_nodes = {}, - -- Indexed arrays for iteration by machine type - PR_nodes = {}, RE_nodes = {}, BA_nodes = {}, - -- Power generation, usage and capacity related variables - supply = 0, demand = 0, battery_charge = 0, battery_charge_max = 0, - BA_count_active = 0, BA_charge_active = 0, battery_supply = 0, battery_demand = 0, - -- Battery status update function - update_battery = update_battery, - -- Network activation and excution control - timeout = 0, skip = 0, lag = 0, average_lag = sma(5) - } - -- Add first cable (one that is holding network id) and build network - local queue = {} - add_cable_node(network.all_nodes, technic.network2pos(network_id), network_id, queue) - technic.add_network_branch(queue, network) + local network = networks[network_id] + if network and not network.queue then + -- Network exists complete and cached + return network.PR_nodes, network.BA_nodes, network.RE_nodes + elseif not network then + -- Build new network if network does not exist + technic.remove_network(network_id) + local sw_pos = technic.network2sw_pos(network_id) + local tier = sw_pos and technic.sw_pos2tier(sw_pos) + if not tier then + -- Failed to get initial cable node for network + return + end + network = { + -- Build queue + queue = {}, + -- Basic network data and lookup table for attached nodes (no switching stations) + id = network_id, tier = tier, all_nodes = {}, + -- Indexed arrays for iteration by machine type + PR_nodes = {}, RE_nodes = {}, BA_nodes = {}, + -- Power generation, usage and capacity related variables + supply = 0, demand = 0, battery_charge = 0, battery_charge_max = 0, + BA_count_active = 0, BA_charge_active = 0, battery_supply = 0, battery_demand = 0, + -- Battery status update function + update_battery = update_battery, + -- Network activation and excution control + timeout = 0, skip = 0, lag = 0, average_lag = sma(5) + } + -- Add first cable (one that is holding network id) and build network + add_cable_node(technic.network2pos(network_id), network) + end + -- Continue building incomplete network + technic.add_network_branch(network.queue, network) network.battery_count = #network.BA_nodes -- Add newly built network to cache array networks[network_id] = network - -- And return producers, batteries and receivers (should this simply return network?) - return network.PR_nodes, network.BA_nodes, network.RE_nodes + if not network.queue then + -- And return producers, batteries and receivers (should this simply return network?) + return network.PR_nodes, network.BA_nodes, network.RE_nodes + end end -- @@ -426,7 +485,7 @@ function technic.network_run(network_id) local network if tier then PR_nodes, BA_nodes, RE_nodes = get_network(network_id, tier) - if technic.is_overloaded(network_id) then return end + if not PR_nodes or technic.is_overloaded(network_id) then return end network = networks[network_id] else --dprint("Not connected to a network") diff --git a/technic/spec/fixtures/mineunit b/technic/spec/fixtures/mineunit index c901b3b1..8a648657 160000 --- a/technic/spec/fixtures/mineunit +++ b/technic/spec/fixtures/mineunit @@ -1 +1 @@ -Subproject commit c901b3b1f9e80f75eb8c45c5dfd55330f475c01e +Subproject commit 8a648657060296f088d3a03234fcb351c82457dd diff --git a/technic/spec/network_spec.lua b/technic/spec/network_spec.lua index 48ea304c..5687a20c 100644 --- a/technic/spec/network_spec.lua +++ b/technic/spec/network_spec.lua @@ -163,255 +163,202 @@ describe("Power network helper", function() end) --- Clean up, left following here just for easy copy pasting stuff from previous proj - ---[[ -describe("Metatool API protection", function() - - it("metatool.is_protected bypass privileges", function() - local value = metatool.is_protected(ProtectedPos(), Player(), "test_priv", true) - assert.equals(false, value) - end) - - it("metatool.is_protected no bypass privileges", function() - local value = metatool.is_protected(ProtectedPos(), Player(), "test_priv2", true) - assert.equals(true, value) - end) - - it("metatool.is_protected bypass privileges, unprotected", function() - local value = metatool.is_protected(UnprotectedPos(), Player(), "test_priv", true) - assert.equals(false, value) - end) - - it("metatool.is_protected no bypass privileges, unprotected", function() - local value = metatool.is_protected(UnprotectedPos(), Player(), "test_priv2", true) - assert.equals(false, value) - end) - -end) - -describe("Metatool API tool namespace", function() - - it("Create invalid namespace", function() - local tool = { ns = metatool.ns, name = 'invalid' } - local value = tool:ns("invalid", { - testkey = "testvalue" +describe("technic.merge_networks", function() + + describe("function behavior", function() + + world.layout({ + {{x=100,y=110,z=170}, "technic:hv_cable"}, + {{x=101,y=110,z=170}, "technic:hv_cable"}, + {{x=102,y=110,z=170}, "technic:hv_cable"}, + {{x=103,y=110,z=170}, "technic:hv_cable"}, + {{x=104,y=110,z=170}, "technic:hv_cable"}, + {{x=100,y=111,z=170}, "technic:switching_station"}, + {{x=101,y=111,z=170}, "technic:hv_generator"}, + {{x=102,y=111,z=170}, "technic:hv_generator"}, + + {{x=100,y=120,z=180}, "technic:hv_cable"}, + {{x=101,y=120,z=180}, "technic:hv_cable"}, + {{x=102,y=120,z=180}, "technic:hv_cable"}, + {{x=103,y=120,z=180}, "technic:hv_cable"}, + {{x=104,y=120,z=180}, "technic:hv_cable"}, + {{x=100,y=121,z=180}, "technic:switching_station"}, + {{x=101,y=121,z=180}, "technic:hv_generator"}, + {{x=102,y=121,z=180}, "technic:hv_generator"}, + + {{x=110,y=130,z=190}, "technic:hv_cable"}, + {{x=111,y=130,z=190}, "technic:hv_cable"}, + {{x=112,y=130,z=190}, "technic:hv_cable"}, + {{x=113,y=130,z=190}, "technic:hv_cable"}, + {{x=114,y=130,z=190}, "technic:hv_cable"}, + {{x=110,y=131,z=190}, "technic:switching_station"}, + {{x=111,y=131,z=190}, "technic:hv_generator"}, + {{x=112,y=131,z=190}, "technic:hv_generator"}, }) - assert.is_nil(metatool:ns("testns")) - end) - - it("Get nonexistent namespace", function() - assert.is_nil(metatool.ns("nonexistent")) - end) - - it("Create tool namespace", function() - -- FIXME: Hack to get fake tool available, replace with real tool - local tool = { ns = metatool.ns, name = 'mytool' } - metatool.tools["metatool:mytool"] = tool - -- Actual tests - local value = tool:ns({ - testkey = "testvalue" - }) - local expected = { - testkey = "testvalue" - } - assert.same(expected, metatool.ns("mytool")) - end) - -end) - -describe("Metatool API tool registration", function() - - it("Register tool default configuration", function() - -- Tool registration - local definition = { - description = 'UnitTestTool Description', - name = 'UnitTestTool', - texture = 'utt.png', - recipe = {{'air'},{'air'},{'air'}}, - on_read_node = function(tooldef, player, pointed_thing, node, pos) - local data, group = tooldef:copy(node, pos, player) - return data, group, "on_read_node description" - end, - on_write_node = function(tooldef, data, group, player, pointed_thing, node, pos) - tooldef:paste(node, pos, player, data, group) - end, - } - local tool = metatool:register_tool('testtool0', definition) - - assert.is_table(tool) - assert.equals("metatool:testtool0", tool.name) - assert.is_table(tool) - assert.equals(definition.description, tool.description) - assert.equals(definition.name, tool.nice_name) - assert.equals(definition.on_read_node, tool.on_read_node) - assert.equals(definition.on_write_node, tool.on_write_node) - - -- Test configurable tool attributes - assert.is_nil(tool.privs) - assert.same({}, tool.settings) + local net1_id = technic.create_network({x=100,y=111,z=170}) + local net2_id = technic.create_network({x=100,y=121,z=180}) + local net3_id = technic.create_network({x=110,y=131,z=190}) + assert.is_number(net1_id) + assert.is_number(net2_id) + assert.is_number(net3_id) + local net1 = technic.networks[net1_id] + local net2 = technic.networks[net2_id] + local net3 = technic.networks[net3_id] + assert.is_table(net1) + assert.is_table(net2) + assert.is_table(net3) + + it("merges networks", function() + -- Verify generated data before starting + assert.equals(2, #net1.PR_nodes) + assert.equals(7, count(net1.all_nodes)) + assert.equals(2, #net2.PR_nodes) + assert.equals(7, count(net2.all_nodes)) + -- Merge networks + technic.merge_networks(net1, net2) + -- Either one of merged networks disappeared + assert.is_nil(technic.networks[net1_id] and technic.networks[net2_id]) + assert.is_table(technic.networks[net1_id] or technic.networks[net2_id]) + -- Merged network exists + assert.is_table(technic.networks[net1_id]) + -- Nodes have been moved to other network + assert.equals(4, #net1.PR_nodes) + assert.equals(14, count(net1.all_nodes)) + end) - -- Namespace creation - local mult = function(a,b) return a * b end - tool:ns({ k1 = "v1", fn = mult }) + it("merges networks again", function() + local merged_net_id = technic.sw_pos2network({x=100,y=111,z=170}) + local merged_net = technic.networks[merged_net_id] + -- Verify generated data before starting + assert.equals(2, #net3.PR_nodes) + assert.equals(7, count(net3.all_nodes)) + assert.equals(4, #merged_net.PR_nodes) + assert.equals(14, count(merged_net.all_nodes)) + -- Merge networks + technic.merge_networks(merged_net, net3) + -- Either one of merged networks disappeared + assert.is_nil(technic.networks[merged_net_id] and technic.networks[net3_id]) + assert.is_table(technic.networks[merged_net_id] or technic.networks[net3_id]) + -- Merged network exists + assert.is_table(technic.networks[merged_net_id]) + -- Nodes have been moved to other network + assert.equals(6, #merged_net.PR_nodes) + assert.equals(21, count(merged_net.all_nodes)) + end) - -- Retrieve namespace and and execute tests - local ns = metatool.ns("testtool0") - assert.same({ k1 = "v1", fn = mult }, ns) - assert.equals(8, ns.fn(2,4)) end) - it("Register tool with configuration", function() - -- Tool registration - local definition = { - description = 'UnitTestTool Description', - name = 'UnitTestTool', - texture = 'utt.png', - recipe = {{'air'},{'air'},{'air'}}, - on_read_node = function(tooldef, player, pointed_thing, node, pos) - local data, group = tooldef:copy(node, pos, player) - return data, group, "on_read_node description" - end, - on_write_node = function(tooldef, data, group, player, pointed_thing, node, pos) - tooldef:paste(node, pos, player, data, group) - end, - } - local tool = metatool:register_tool('testtool2', definition) + describe("network building behavior", function() - assert.is_table(tool) - assert.equals("metatool:testtool2", tool.name) - - assert.is_table(tool) - assert.equals(definition.description, tool.description) - assert.equals(definition.name, tool.nice_name) - assert.equals(definition.on_read_node, tool.on_read_node) - assert.equals(definition.on_write_node, tool.on_write_node) - - -- Test configurable tool attributes - assert.equals("test_testtool2_privs", tool.privs) - local expected_settings = { - extra_config_key = "testtool2_extra_config_value", - } - assert.same(expected_settings, tool.settings) + -- Hijack `minetest.get_us_time` for this test set. + -- insulate(...) does not seem to work here and finally(...) can apparently + -- only be used inside it(...) so we go with strict_setup/strict_teardown. - -- Namespace creation - local sum = function(a,b) return a + b end - tool:ns({ k1 = "v1", fn = sum }) - - -- Retrieve namespace and and execute tests - local ns = metatool.ns("testtool2") - assert.same({ k1 = "v1", fn = sum }, ns) - assert.equals(9, ns.fn(2,7)) - end) + local old_minetest_get_us_time = _G.minetest.get_us_time + strict_setup(function() + local fake_us_time = 0 + local fake_us_time_increment = 1000 * 1000 * 10 -- 10 seconds + _G.minetest.get_us_time = function() + fake_us_time = fake_us_time + fake_us_time_increment + return fake_us_time + end + end) -end) + strict_teardown(function() + _G.minetest.get_us_time = old_minetest_get_us_time + end) -describe("Metatool API node registration", function() - - it("Register node default configuration", function() - local tool = metatool.tool("testtool0") - assert.is_table(tool) - assert.equals("metatool:testtool0", tool.name) - assert.is_table(tool) - - local definition = { - name = 'testnode1', - nodes = { - "testnode1", - "nonexistent1", - "testnode2", - "nonexistent2", - }, - tooldef = { - group = 'test node', - protection_bypass_write = "default_bypass_write_priv", - copy = function(node, pos, player) - print("nodedef copy callback executed") - end, - paste = function(node, pos, player, data) - print("nodedef paste callback executed") - end, - } + local layout = { + {{x=180,y=10,z=190}, "technic:hv_cable"}, + {{x=181,y=10,z=190}, "technic:hv_cable"}, + {{x=182,y=10,z=190}, "technic:hv_cable"}, + {{x=180,y=11,z=190}, "technic:switching_station"}, + {{x=181,y=11,z=190}, "technic:hv_generator"}, + {{x=182,y=11,z=190}, "technic:hv_generator"}, } - tool:load_node_definition(definition) - - assert.is_table(tool.nodes) - assert.is_table(tool.nodes.testnode1) - assert.is_table(tool.nodes.testnode2) - assert.is_nil(tool.nodes.nonexistent1) - assert.is_nil(tool.nodes.nonexistent2) - - assert.is_function(tool.nodes.testnode1.before_read) - assert.is_function(tool.nodes.testnode2.before_write) - - assert.equals(definition.tooldef.copy, tool.nodes.testnode1.copy) - assert.equals(definition.tooldef.paste, tool.nodes.testnode2.paste) - assert.equals("default_bypass_write_priv", definition.tooldef.protection_bypass_write) + world.clear() + world.add_layout(layout) + world.add_layout(layout, {x=3,y=0,z=0}) + world.add_layout(layout, {x=6,y=0,z=0}) + + local net1_id + local net2_id + + it("stops network building after first iteration", function() + net1_id = technic.create_network({x=180,y=11,z=190}) + assert.is_number(net1_id) + local net1 = technic.networks[net1_id] + assert.is_table(net1) + + -- Only first iteration passed, 2 cables (initial + iteration) added + assert.equals(0, #net1.PR_nodes) + assert.equals(2, count(net1.all_nodes)) + -- And queue contains next cable + assert.is_table(net1.queue) + assert.equals(1, #net1.queue) + assert.same({x=181,y=10,z=190}, net1.queue[1]) + end) - local expected_settings = { - protection_bypass_write = "default_bypass_write_priv" - } - assert.same(expected_settings, tool.nodes.testnode1.settings) - assert.same(expected_settings, tool.nodes.testnode2.settings) + it("continues network building", function() + technic.build_network(net1_id) + local net1 = technic.networks[net1_id] + assert.is_table(net1) + + -- Only first iteration passed, nodes around queue added: 1 cable and 1 generator + assert.equals(1, #net1.PR_nodes) + assert.equals(4, count(net1.all_nodes)) + -- And queue contains next cable + assert.is_table(net1.queue) + assert.equals(1, #net1.queue) + assert.same({x=182,y=10,z=190}, net1.queue[1]) + end) - end) + it("merges with second network", function() + -- Execute 2 build iterations + net2_id = technic.create_network({x=183,y=11,z=190}) + technic.build_network(net2_id) + assert.is_number(net2_id) + + -- Networks merged + local net1 = technic.networks[net1_id] + assert.is_nil(net1) + local net2 = technic.networks[net2_id] + assert.is_table(net2) + + -- Check that ned count is higher than last net1 node count + assert.is_true(#net2.PR_nodes > 1) + assert.is_true(count(net2.all_nodes) > 4) + + -- No duplicates added + for k1,v1 in pairs(net2.PR_nodes) do for k2,v2 in pairs(net2.PR_nodes) do + assert.is_true(k1 == k2 or v1.x ~= v2.x or v1.y ~= v2.y or v1.z ~= v2.z) + end end + end) - it("Register node with configuration", function() - local tool = metatool.tool("testtool2") - assert.is_table(tool) - assert.equals("metatool:testtool2", tool.name) - assert.is_table(tool) - - local definition = { - name = 'testnode2', - nodes = { - "testnode1", - "nonexistent1", - "testnode2", - "nonexistent2", - }, - tooldef = { - group = 'test node', - protection_bypass_write = "default_bypass_write_priv", - copy = function(node, pos, player) - print("nodedef copy callback executed") - end, - paste = function(node, pos, player, data) - print("nodedef paste callback executed") - end, - } - } - tool:load_node_definition(definition) - - assert.is_table(tool.nodes) - assert.is_table(tool.nodes.testnode1) - assert.is_table(tool.nodes.testnode2) - assert.is_nil(tool.nodes.nonexistent1) - assert.is_nil(tool.nodes.nonexistent2) - - assert.is_function(tool.nodes.testnode1.before_read) - assert.is_function(tool.nodes.testnode2.before_write) - - assert.equals(definition.tooldef.copy, tool.nodes.testnode1.copy) - assert.equals(definition.tooldef.paste, tool.nodes.testnode2.paste) - assert.equals("testtool2_testnode2_bypass_write", tool.nodes.testnode1.protection_bypass_write) - assert.equals("testtool2_testnode2_bypass_write", tool.nodes.testnode2.protection_bypass_write) - assert.equals("testtool2_testnode2_bypass_info", tool.nodes.testnode1.protection_bypass_info) - assert.equals("testtool2_testnode2_bypass_info", tool.nodes.testnode2.protection_bypass_info) - assert.equals("testtool2_testnode2_bypass_read", tool.nodes.testnode1.protection_bypass_read) - assert.equals("testtool2_testnode2_bypass_read", tool.nodes.testnode2.protection_bypass_read) - - local expected_settings = { - protection_bypass_write = "testtool2_testnode2_bypass_write", - protection_bypass_info = "testtool2_testnode2_bypass_info", - protection_bypass_read = "testtool2_testnode2_bypass_read", - } - assert.same(expected_settings, tool.nodes.testnode1.settings) - assert.same(expected_settings, tool.nodes.testnode2.settings) + it("finishes network build", function() + local net3_id = technic.create_network({x=186,y=11,z=190}) + technic.build_network(net3_id) + technic.build_network(net3_id) + + -- Networks merged + local net1 = technic.networks[net1_id] + assert.is_nil(net1) + local net2 = technic.networks[net2_id] + assert.is_nil(net2) + local net3 = technic.networks[net3_id] + assert.is_table(net3) + + -- Check that network build is completed and all nodes added to network + assert.equals(6, #net3.PR_nodes) + assert.equals(15, count(net3.all_nodes)) + + -- No duplicates added + for k1,v1 in pairs(net3.PR_nodes) do for k2,v2 in pairs(net3.PR_nodes) do + assert.is_true(k1 == k2 or v1.x ~= v2.x or v1.y ~= v2.y or v1.z ~= v2.z) + end end + end) end) end) - ---]]