techage/power/power.lua

371 lines
10 KiB
Lua
Raw Normal View History

2019-05-21 01:05:53 +03:00
--[[
TechAge
=======
Copyright (C) 2019 Joachim Stolberg
LGPLv2.1+
See LICENSE.txt for more information
Power distribution and consumption calculation
for any kind of power distribution network
]]--
-- for lazy programmers
local S = function(pos) if pos then return minetest.pos_to_string(pos) end end
local P = minetest.string_to_pos
local M = minetest.get_meta
-- Techage Related Data
local PWR = function(pos) return (minetest.registered_nodes[minetest.get_node(pos).name] or {}).power end
-- Used to determine the already passed nodes while power distribution
local Route = {}
2019-08-15 18:06:58 +03:00
techage.power = {}
-- Consumer States
local STOPPED = 1
local NOPOWER = 2
local RUNNING = 3
2019-06-16 22:06:16 +03:00
2019-05-21 01:05:53 +03:00
local function pos_already_reached(pos)
local key = minetest.hash_node_position(pos)
if not Route[key] then
Route[key] = true
return false
end
return true
end
2019-06-16 22:06:16 +03:00
local function min(val, max)
if val < 0 then return 0 end
if val > max then return max end
return val
end
2019-08-15 18:06:58 +03:00
local function accounting(pos, mem)
-- calculate the primary and secondary supply and demand
2019-08-15 18:06:58 +03:00
mem.mst_supply1 = min(mem.mst_needed1 + mem.mst_needed2, mem.mst_available1)
mem.mst_demand1 = min(mem.mst_needed1, mem.mst_available1 + mem.mst_available2)
mem.mst_supply2 = min(mem.mst_demand1 - mem.mst_supply1, mem.mst_available2)
mem.mst_demand2 = min(mem.mst_supply1 - mem.mst_demand1, mem.mst_available1)
mem.mst_reserve = (mem.mst_available1 + mem.mst_available2) - mem.mst_needed1
--print("needed = "..mem.mst_needed1.."/"..mem.mst_needed2..", available = "..mem.mst_available1.."/"..mem.mst_available2)
--print("supply = "..mem.mst_supply1.."/"..mem.mst_supply2..", demand = "..mem.mst_demand1.."/"..mem.mst_demand2..", reserve = "..mem.mst_reserve)
end
2019-06-16 22:06:16 +03:00
local function connection_walk(pos, clbk)
2019-06-15 17:14:46 +03:00
local mem = tubelib2.get_mem(pos)
mem.interrupted_dirs = mem.interrupted_dirs or {}
2019-05-21 01:05:53 +03:00
if clbk then
2019-06-16 22:06:16 +03:00
clbk(pos, mem)
2019-05-21 01:05:53 +03:00
end
2019-06-15 17:14:46 +03:00
for out_dir,item in pairs(mem.connections or {}) do
if item.pos and not pos_already_reached(item.pos) and
not mem.interrupted_dirs[out_dir] then
2019-06-16 22:06:16 +03:00
connection_walk(item.pos, clbk)
2019-05-21 01:05:53 +03:00
end
end
end
2019-08-15 18:06:58 +03:00
local function consumer_turn_off(pos, mem)
local pwr = PWR(pos)
print("consumer_turn_off")
if pwr and pwr.on_nopower then
pwr.on_nopower(pos, mem)
end
mem.pwr_node_alive_cnt = 0
mem.pwr_state = NOPOWER
end
local function consumer_turn_on(pos, mem)
local pwr = PWR(pos)
print("consumer_turn_on")
if pwr and pwr.on_power then
pwr.on_power(pos, mem)
end
mem.pwr_state = RUNNING
end
2019-06-16 22:06:16 +03:00
-- determine one "generating" node as master (largest hash number)
local function determine_master(pos)
2019-05-21 01:05:53 +03:00
Route = {}
pos_already_reached(pos)
2019-06-16 22:06:16 +03:00
local hash = 0
local master = nil
connection_walk(pos, function(pos, mem)
2019-08-15 18:06:58 +03:00
if (mem.pwr_node_alive_cnt or 0) >= 0 and
(mem.pwr_available or 0) > 0 or
(mem.pwr_available2 or 0) > 0 then -- active generator?
local new = minetest.hash_node_position(pos)
if hash <= new then
hash = new
master = pos
2019-06-16 22:06:16 +03:00
end
2019-08-15 18:06:58 +03:00
end
end)
2019-06-16 22:06:16 +03:00
return master
end
-- store master position on all network nodes
local function store_master(pos, master_pos)
2019-05-21 01:05:53 +03:00
Route = {}
pos_already_reached(pos)
2019-06-16 22:06:16 +03:00
connection_walk(pos, function(pos, mem)
2019-08-15 18:06:58 +03:00
mem.pwr_master_pos = master_pos
mem.pwr_is_master = false
2019-06-16 22:06:16 +03:00
end)
2019-05-21 01:05:53 +03:00
end
2019-08-15 18:06:58 +03:00
local function handle_generator(mst_mem, mem, pos, power_available)
-- for next cycle
mst_mem.mst_available1 = mst_mem.mst_available1 + power_available
-- current cycle
mst_mem.mst_supply1 = mst_mem.mst_supply1 or 0
if mst_mem.mst_supply1 < power_available then
mem.pwr_provided = mst_mem.mst_supply1
mst_mem.mst_supply1 = 0
else
mst_mem.mst_supply1 = mst_mem.mst_supply1 - power_available
mem.pwr_provided = power_available
end
end
local function handle_consumer(mst_mem, mem, pos, power_needed)
print("handle_consumer", mem.pwr_state)
if mem.pwr_state == NOPOWER then
--print("power_needed", power_needed,"mst_mem.demand1", mst_mem.mst_demand1)
-- for next cycle
mst_mem.mst_needed1 = mst_mem.mst_needed1 + power_needed
-- current cycle
if (mst_mem.mst_demand1 or 0) - power_needed >= 0 then
mst_mem.mst_demand1 = (mst_mem.mst_demand1 or 0) - power_needed
consumer_turn_on(pos, mem)
end
elseif mem.pwr_state == RUNNING then
-- for next cycle
mst_mem.mst_needed1 = mst_mem.mst_needed1 + power_needed
-- current cycle
mst_mem.mst_demand1 = (mst_mem.mst_demand1 or 0) - power_needed
if mst_mem.mst_demand1 < 0 then
mst_mem.mst_demand1 = 0
consumer_turn_off(pos, mem)
end
end
end
local function handle_secondary(mst_mem, mem, pos, provides, needed)
-- for next cycle
mst_mem.mst_available2 = (mst_mem.mst_available2 or 0) + provides
mst_mem.mst_needed2 = (mst_mem.mst_needed2 or 0) + needed
-- check as generator
mst_mem.mst_supply2 = mst_mem.mst_supply2 or 0
mst_mem.mst_demand2 = mst_mem.mst_demand2 or 0
if mst_mem.mst_supply2 > 0 then
local val = math.min(provides, mst_mem.mst_supply2)
mst_mem.mst_supply2 = mst_mem.mst_supply2 - val
mem.pwr_provided = val
-- check as consumer
elseif mst_mem.mst_demand2 > 0 then
local val = math.min(needed, mst_mem.mst_demand2)
mst_mem.mst_demand2 = mst_mem.mst_demand2 - val
mem.pwr_provided = -val
else
mem.pwr_provided = 0
end
end
local function trigger_nodes(mst_pos, mst_mem)
2019-06-20 15:01:37 +03:00
Route = {}
2019-08-15 18:06:58 +03:00
pos_already_reached(mst_pos)
connection_walk(mst_pos, function(pos, mem)
mem.pwr_node_alive_cnt = (mem.pwr_node_alive_cnt or 1) - 1
mem.pwr_power_provided_cnt = 2
--print("trigger_nodes", mem.pwr_node_alive_cnt, mem.pwr_available2 or mem.pwr_available or mem.pwr_needed)
if mem.pwr_node_alive_cnt >= 0 then
if mem.pwr_available then
handle_generator(mst_mem, mem, pos, mem.pwr_available)
elseif mem.pwr_needed then
handle_consumer(mst_mem, mem, pos, mem.pwr_needed)
elseif mem.pwr_available2 then
handle_secondary(mst_mem, mem, pos, mem.pwr_available2, mem.pwr_needed2)
2019-06-20 15:01:37 +03:00
end
2019-08-15 18:06:58 +03:00
end
end)
2019-06-20 15:01:37 +03:00
end
2019-06-16 22:06:16 +03:00
2019-08-15 18:06:58 +03:00
local function determine_new_master(pos, mem)
local was_master = mem.pwr_is_master
mem.pwr_is_master = false
2019-06-16 22:06:16 +03:00
local mpos = determine_master(pos)
2019-08-15 18:06:58 +03:00
--print("determine_new_master", S(mpos))
2019-06-16 22:06:16 +03:00
store_master(pos, mpos)
if mpos then
2019-08-15 18:06:58 +03:00
tubelib2.get_mem(mpos).pwr_is_master = true
elseif was_master then -- no master any more
-- delete data
local mmem = tubelib2.get_mem(mpos)
mmem.mst_supply1 = 0
mmem.mst_supply2 = 0
mmem.mst_reserve = 0
2019-06-16 22:06:16 +03:00
end
2019-08-15 18:06:58 +03:00
return was_master or mem.pwr_is_master
end
-- called from master position
local function power_distribution(pos, mem)
mem.mst_needed1 = 0
mem.mst_needed2 = 0
mem.mst_available1 = 0
mem.mst_available2 = 0
trigger_nodes(pos, mem)
accounting(pos, mem)
2019-06-16 22:06:16 +03:00
end
2019-05-21 01:05:53 +03:00
--
2019-08-15 18:06:58 +03:00
-- Power API functions
2019-05-21 01:05:53 +03:00
--
2019-08-15 18:06:58 +03:00
-- To be called for each network change from any node
function techage.power.network_changed(pos, mem)
print("network_changed")
mem.pwr_node_alive_cnt = (mem.pwr_cycle_time or 2)/2 + 1
if determine_new_master(pos, mem) then -- new master?
power_distribution(pos, mem)
elseif not next(mem.connections) then -- isolated?
if mem.pwr_needed then -- consumer?
consumer_turn_off(pos, mem)
2019-05-21 23:04:05 +03:00
end
end
end
2019-08-15 18:06:58 +03:00
--
-- Generator related functions
--
function techage.power.generator_start(pos, mem, available)
mem.pwr_node_alive_cnt = 2
mem.pwr_cycle_time = 2
mem.pwr_available = available
if determine_new_master(pos, mem) then -- new master
power_distribution(pos, mem)
end
end
2019-08-15 18:06:58 +03:00
function techage.power.generator_stop(pos, mem)
mem.pwr_node_alive_cnt = 0
mem.pwr_available = 0
if determine_new_master(pos, mem) then -- last available master
power_distribution(pos, mem)
end
end
2019-08-15 18:06:58 +03:00
function techage.power.generator_alive(pos, mem)
mem.pwr_node_alive_cnt = 2
if mem.pwr_is_master then
power_distribution(pos, mem)
2019-06-16 22:06:16 +03:00
end
2019-08-15 18:06:58 +03:00
return mem.pwr_provided
2019-06-16 22:06:16 +03:00
end
2019-08-15 18:06:58 +03:00
--
-- Consumer related functions
--
function techage.power.consumer_alive(pos, mem)
print("consumer_alive", mem.pwr_power_provided_cnt)
mem.pwr_power_provided_cnt = (mem.pwr_power_provided_cnt or 0) - (mem.pwr_cycle_time or 2)/2
if mem.pwr_power_provided_cnt >= 0 then
mem.pwr_node_alive_cnt = (mem.pwr_cycle_time or 2)/2 + 1
2019-06-16 22:06:16 +03:00
else
2019-08-15 18:06:58 +03:00
consumer_turn_off(pos, mem)
2019-06-16 22:06:16 +03:00
end
end
2019-08-15 18:06:58 +03:00
function techage.power.consumer_start(pos, mem, cycle_time, needed)
mem.pwr_cycle_time = cycle_time
mem.pwr_power_provided_cnt = 0
mem.pwr_node_alive_cnt = 2
mem.pwr_needed = needed
mem.pwr_state = NOPOWER
end
2019-06-16 22:06:16 +03:00
2019-08-15 18:06:58 +03:00
function techage.power.consumer_stop(pos, mem)
mem.pwr_power_provided_cnt = 0
mem.pwr_node_alive_cnt = 0
mem.pwr_needed = 0
mem.pwr_state = STOPPED
2019-06-16 22:06:16 +03:00
end
2019-08-15 18:06:58 +03:00
-- Lamp related function to speed up the turn on
function techage.power.power_available(pos, mem, needed)
if mem.pwr_master_pos and (mem.pwr_power_provided_cnt or 0) > 0 then
mem = tubelib2.get_mem(mem.pwr_master_pos)
if (mem.mst_reserve or 0) - needed >= 0 then
mem.mst_reserve = mem.mst_reserve - needed
return true
2019-06-17 20:57:08 +03:00
end
2019-06-16 22:06:16 +03:00
end
2019-06-17 20:57:08 +03:00
return false
2019-06-16 22:06:16 +03:00
end
2019-08-15 18:06:58 +03:00
-- Power terminal function
function techage.power.power_accounting(pos, mem)
if mem.pwr_master_pos then
mem = tubelib2.get_mem(mem.pwr_master_pos)
return {
prim_available = mem.mst_available1,
sec_available = mem.mst_available2,
prim_needed = mem.mst_needed1,
sec_needed = mem.mst_needed2,
}
end
2019-08-15 18:06:58 +03:00
return {
prim_available = 0,
sec_available = 0,
prim_needed = 0,
sec_needed = 0,
}
end
2019-08-15 18:06:58 +03:00
--
-- Akku related functions
--
function techage.power.secondary_start(pos, mem, available, needed)
mem.pwr_node_alive_cnt = 2
mem.pwr_could_provide = available
mem.pwr_could_need = needed
if determine_new_master(pos, mem) then -- new master
power_distribution(pos, mem)
end
2019-05-21 01:05:53 +03:00
end
2019-08-15 18:06:58 +03:00
function techage.power.secondary_stop(pos, mem)
mem.pwr_node_alive_cnt = 0
mem.pwr_could_provide = 0
mem.pwr_could_need = 0
if determine_new_master(pos, mem) then -- last available master
power_distribution(pos, mem)
2019-06-16 22:06:16 +03:00
end
2019-08-15 18:06:58 +03:00
end
2019-06-16 22:06:16 +03:00
2019-08-15 18:06:58 +03:00
function techage.power.secondary_alive(pos, mem, capa_curr, capa_max)
--print("secondary_alive")
if capa_curr >= capa_max then
mem.pwr_available2, mem.pwr_needed2 = mem.pwr_could_provide, 0 -- can provide only
elseif capa_curr <= 0 then
mem.pwr_available2, mem.pwr_needed2 = 0, mem.pwr_could_need -- can deliver only
2019-06-16 22:06:16 +03:00
else
2019-08-15 18:06:58 +03:00
mem.pwr_available2, mem.pwr_needed2 = mem.pwr_could_provide, mem.pwr_could_need
2019-06-16 22:06:16 +03:00
end
2019-08-15 18:06:58 +03:00
mem.pwr_node_alive_cnt = 2
if mem.pwr_is_master then
--print("secondary_alive is master")
power_distribution(pos, mem)
end
return mem.pwr_provided
end