Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Async network build, remove switch_max_range #136

Merged
merged 3 commits into from
Dec 9, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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

Expand Down
1 change: 0 additions & 1 deletion technic/config.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
207 changes: 133 additions & 74 deletions technic/machines/network.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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]
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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},
Expand All @@ -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
Expand All @@ -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)
Expand All @@ -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
Expand Down Expand Up @@ -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

--
Expand Down Expand Up @@ -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")
Expand Down
2 changes: 1 addition & 1 deletion technic/spec/fixtures/mineunit
Loading