elepower/elepower_papi/network.lua

399 lines
11 KiB
Lua

-- Network graphs are built eminating from provider nodes.
--[[
TODO:
Currently, there's a problem where storage nodes are allowed to create their own graph.
When placing the storage onto a cable, it will add itself to the graph of that cable.
But, when placing a cable onto the storage, that cable is added to the storage's own graph
and thus cannot be connected to the previous graph.
]]
-- Network cache
ele.graphcache = {devices = {}}
---------------------
-- Graph Functions --
---------------------
local function clear_networks_from_node(pos)
local meta = minetest.get_meta(pos)
meta:set_string("network_id", "")
end
local function add_node(nodes, pos, pnodeid)
local node_id = minetest.hash_node_position(pos)
if ele.graphcache.devices[node_id] and ele.graphcache.devices[node_id] ~= pnodeid then return end
ele.graphcache.devices[node_id] = pnodeid
if nodes[node_id] then
return false
end
nodes[node_id] = pos
return true
end
local function add_conductor_node(nodes, pos, pnodeid, queue)
if add_node(nodes, pos, pnodeid) then
queue[#queue + 1] = pos
end
end
local function check_node(users, providers, all_nodes, pos, pr_pos, pnodeid, queue)
ele.helpers.get_or_load_node(pos)
local node = minetest.get_node(pos)
local meta = minetest.get_meta(pos)
if ele.helpers.get_item_group(node.name, "ele_conductor") then
local nodedef = minetest.registered_nodes[node.name]
local ptransfer = 0
if nodedef.elepower_transfer then
ptransfer = nodedef.elepower_transfer
end
add_conductor_node(all_nodes, pos, pnodeid, queue)
return
end
if not ele.helpers.get_item_group(node.name, "ele_machine") then
return
end
-- Don't add already networked nodes to this network
if meta:get_string("network_id") ~= "" and meta:get_string("network_id") ~= pnodeid then
return
end
meta:set_string("network_id", pnodeid)
if ele.helpers.get_item_group(node.name, "ele_user") then
add_node(users, pos, pnodeid)
elseif ele.helpers.get_item_group(node.name, "ele_provider") then
if ele.helpers.get_item_group(node.name, "ele_storage") then
-- Storage gets added to users, for now
add_node(users, pos, pnodeid)
else
add_node(providers, pos, pnodeid)
end
end
end
local function traverse_network(users, providers, all_nodes, pos, pr_pos, pnodeid, queue)
local positions = {
{x=pos.x+1, y=pos.y, z=pos.z},
{x=pos.x-1, y=pos.y, z=pos.z},
{x=pos.x, y=pos.y+1, z=pos.z},
{x=pos.x, y=pos.y-1, z=pos.z},
{x=pos.x, y=pos.y, z=pos.z+1},
{x=pos.x, y=pos.y, z=pos.z-1}}
for _, cur_pos in pairs(positions) do
check_node(users, providers, all_nodes, cur_pos, pr_pos, pnodeid, queue)
end
end
local function power_networks(pr_pos, positions)
local provider = minetest.get_node(pr_pos)
local pnodeid = minetest.pos_to_string(pr_pos)
if ele.graphcache[pnodeid] then
local cached = ele.graphcache[pnodeid]
return cached.users, cached.providers
end
local users = {}
local providers = {}
local queue = {}
local all_nodes = {}
for pos in pairs(positions) do
queue = {}
local node = minetest.get_node(pos)
if node and ele.helpers.get_item_group(node.name, "ele_conductor") then
add_conductor_node(all_nodes, pos, pnodeid, queue)
elseif node and ele.helpers.get_item_group(node.name, "ele_machine") then
queue = {pr_pos}
end
while next(queue) do
local to_visit = {}
for _, posi in ipairs(queue) do
traverse_network(users, providers, all_nodes, posi, pr_pos, pnodeid, to_visit)
end
queue = to_visit
end
end
-- Add self to providers
local prov_id = minetest.hash_node_position(pr_pos)
ele.graphcache.devices[prov_id] = pnodeid
providers[prov_id] = pr_pos
users = ele.helpers.flatten(users)
providers = ele.helpers.flatten(providers)
all_nodes = ele.helpers.flatten(all_nodes)
ele.graphcache[pnodeid] = {all_nodes = all_nodes, users = users, providers = providers}
return users, providers
end
-----------------------
-- Main Transfer ABM --
-----------------------
local function give_node_power(pos, available)
local user_meta = minetest.get_meta(pos)
local capacity = ele.helpers.get_node_property(user_meta, pos, "capacity")
local inrush = ele.helpers.get_node_property(user_meta, pos, "inrush")
local storage = user_meta:get_int("storage")
local total_add = 0
if available >= inrush then
total_add = inrush
elseif available < inrush then
total_add = inrush - available
end
if total_add + storage > capacity then
total_add = (total_add + storage) - capacity
end
if storage >= capacity then
total_add = 0
storage = capacity
end
return total_add, storage
end
minetest.register_abm({
nodenames = {"group:ele_provider"},
label = "elepowerPowerGraphSource",
interval = 1,
chance = 1,
action = function(pos, node, active_object_count, active_object_count_wider)
local meta = minetest.get_meta(pos)
local meta1 = nil
-- Check if a provider is attached to a network.
-- If that network has been abolished, we will use this node as the network's root this time.
local netwrkto = meta:get_string("ele_network")
if netwrkto ~= "" and netwrkto ~= nil then
if not ele.helpers.get_item_group(node.name, "ele_storage") then
local lpos = minetest.string_to_pos(netwrkto)
if ele.helpers.get_item_group(minetest.get_node(lpos).name, "ele_provider") then
return
else
ele.graphcache[netwrkto] = nil
end
meta:set_string("ele_network", "")
end
end
local users = {}
local providers = {}
local providerdef = minetest.registered_nodes[node.name]
-- TODO: Customizable output sides
local positions = {
{x=pos.x, y=pos.y-1, z=pos.z},
{x=pos.x, y=pos.y+1, z=pos.z},
{x=pos.x-1, y=pos.y, z=pos.z},
{x=pos.x+1, y=pos.y, z=pos.z},
{x=pos.x, y=pos.y, z=pos.z-1},
{x=pos.x, y=pos.y, z=pos.z+1}
}
local ntwks = {}
local errored = false
local nw_branches = 0
for _,pos1 in pairs(positions) do
local name = node.name
local networked = ele.helpers.get_item_group(name, "ele_machine") or ele.helpers.get_item_group(name, "ele_conductor")
if networked then
ntwks[pos1] = true
nw_branches = nw_branches + 1
end
end
if errored then
return
end
if nw_branches == 0 then
minetest.forceload_free_block(pos)
return
else
minetest.forceload_block(pos)
end
users, providers = power_networks(pos, ntwks)
-- Calculate power data
local pw_supply = 0
local pw_demand = 0
for _, spos in ipairs(providers) do
local smeta = minetest.get_meta(spos)
local pw_storage = smeta:get_int("storage")
local p_output = ele.helpers.get_node_property(smeta, spos, "output")
if p_output and pw_storage >= p_output then
pw_supply = pw_supply + p_output
elseif p_output and pw_storage < p_output then
pw_supply = pw_supply + pw_storage
end
end
-- Give power to users
for _,ndv in ipairs(users) do
if pw_demand >= pw_supply then
break
end
local user_gets, user_storage = give_node_power(ndv, pw_supply - pw_demand)
pw_demand = pw_demand + user_gets
local user_meta = minetest.get_meta(ndv)
user_meta:set_int("storage", user_storage + user_gets)
if user_gets > 0 then
-- Set timer on this node
local t = minetest.get_node_timer(ndv)
if not t:is_started() then
t:start(1.0)
end
end
end
-- Take the power from a provider node
if pw_demand > 0 then
for _, spos in ipairs(providers) do
local smeta = minetest.get_meta(spos)
local pw_storage = smeta:get_int("storage")
if pw_storage >= pw_demand then
smeta:set_int("storage", pw_storage - pw_demand)
else
pw_demand = pw_demand - pw_storage
smeta:set_int("storage", 0)
end
minetest.get_node_timer(spos):start(1.0)
end
end
end,
})
local function check_connections(pos)
local connections = {}
local positions = {
{x=pos.x+1, y=pos.y, z=pos.z},
{x=pos.x-1, y=pos.y, z=pos.z},
{x=pos.x, y=pos.y+1, z=pos.z},
{x=pos.x, y=pos.y-1, z=pos.z},
{x=pos.x, y=pos.y, z=pos.z+1},
{x=pos.x, y=pos.y, z=pos.z-1}}
for _,connected_pos in pairs(positions) do
local name = minetest.get_node(connected_pos).name
if ele.helpers.get_item_group(name, "ele_conductor") or ele.helpers.get_item_group(name, "ele_machine") then
table.insert(connections, connected_pos)
end
end
return connections
end
-- Update networks when a node has been placed or removed
function ele.clear_networks(pos)
local node = minetest.get_node(pos)
local meta = minetest.get_meta(pos)
local name = node.name
local placed = name ~= "air"
local positions = check_connections(pos)
if #positions < 1 then return end
local dead_end = #positions == 1
for _,connected_pos in pairs(positions) do
local net = ele.graphcache.devices[minetest.hash_node_position(connected_pos)] or minetest.pos_to_string(connected_pos)
if net and ele.graphcache[net] then
if dead_end and placed then
-- Dead end placed, add it to the network
-- Get the network
local node_at = minetest.get_node(positions[1])
local network_id = ele.graphcache.devices[minetest.hash_node_position(positions[1])] or minetest.pos_to_string(positions[1])
if not network_id or not ele.graphcache[network_id] then
-- We're evidently not on a network, nothing to add ourselves to
return
end
local c_pos = minetest.string_to_pos(network_id)
local network = ele.graphcache[network_id]
-- Actually add it to the (cached) network
-- This is similar to check_node_subp
ele.graphcache.devices[minetest.hash_node_position(pos)] = network_id
pos.visited = 1
if ele.helpers.get_item_group(name, "ele_conductor") then
table.insert(network.all_nodes, pos)
end
if ele.helpers.get_item_group(name, "ele_machine") then
meta:set_string("ele_network", network_id)
if ele.helpers.get_item_group(name, "ele_provider") then
if ele.helpers.get_item_group(name, "ele_storage") then
-- TODO: Add storage to users for now
table.insert(network.users, pos)
else
table.insert(network.providers, pos)
end
elseif ele.helpers.get_item_group(name, "ele_user") then
table.insert(network.users, pos)
end
end
elseif dead_end and not placed then
-- Dead end removed, remove it from the network
-- Get the network
local network_id = ele.graphcache.devices[minetest.hash_node_position(positions[1])] or minetest.pos_to_string(positions[1])
if not network_id or not ele.graphcache[network_id] then
-- We're evidently not on a network, nothing to remove ourselves from
return
end
local network = ele.graphcache[network_id]
-- The network was deleted.
if network_id == minetest.pos_to_string(pos) then
for _,v in pairs(network.all_nodes) do
local pos1 = minetest.hash_node_position(v)
clear_networks_from_node(v)
ele.graphcache.devices[pos1] = nil
end
ele.graphcache[network_id] = nil
return
end
-- Search for and remove device
ele.graphcache.devices[minetest.hash_node_position(pos)] = nil
for tblname,table in pairs(network) do
if type(table) == "table" then
for devicenum,device in pairs(table) do
if vector.equals(device, pos) then
table[devicenum] = nil
end
end
end
end
else
-- Not a dead end, so the whole network needs to be recalculated
for _,v in pairs(ele.graphcache[net].all_nodes) do
local pos1 = minetest.hash_node_position(v)
clear_networks_from_node(v)
ele.graphcache.devices[pos1] = nil
end
ele.graphcache[net] = nil
end
end
end
end