From cd1b2aa333376020cdd50cb42a0693fa6a8c39a2 Mon Sep 17 00:00:00 2001 From: Danielv123 Date: Tue, 27 Jan 2026 15:42:41 +0100 Subject: [PATCH] Balance all accumulators in the electric network instead of EEI abuse --- controller.ts | 2 +- instance.ts | 2 + module/edge/create_power_link.lua | 2 +- module/edge/power_box.lua | 88 +++++++--------- module/edge/power_link.lua | 166 ++++++++++-------------------- 5 files changed, 95 insertions(+), 165 deletions(-) diff --git a/controller.ts b/controller.ts index f4f2cd2..ea81095 100644 --- a/controller.ts +++ b/controller.ts @@ -263,7 +263,7 @@ export class ControllerPlugin extends BaseControllerPlugin { // Find reachable_sources [...destinations.values()].forEach((dest) => { // Add targets from adjacent nodes - dest.targets.forEach((target) => dest.reachable_targets.set(target, 1)); + dest.targets.forEach((target) => { dest.reachable_targets.set(target, 1); }); }); [...destinations.values()].forEach((dest) => { diff --git a/instance.ts b/instance.ts index ec4bd22..4b61f82 100644 --- a/instance.ts +++ b/instance.ts @@ -54,6 +54,7 @@ type FluidTransfer = { type PowerTransfer = { offset: number, energy?: number, + capacity?: number, amount_balanced?: number, } @@ -170,6 +171,7 @@ function mergePowerTransfers( } // When sending amount we send the current amount in the tank, hence we want to overwrite instead of adding here if (powerTransfer.energy !== undefined) { pending.energy = powerTransfer.energy; } + if (powerTransfer.capacity !== undefined) { pending.capacity = powerTransfer.capacity; } // Amount balanced is the amount of fluid we have added on the current instance. This one needs to be additive. if (powerTransfer.amount_balanced !== undefined) { if (pending.amount_balanced !== undefined) { diff --git a/module/edge/create_power_link.lua b/module/edge/create_power_link.lua index 084702c..a773de7 100644 --- a/module/edge/create_power_link.lua +++ b/module/edge/create_power_link.lua @@ -2,7 +2,7 @@ local clusterio_api = require("modules/clusterio/api") local power_box = require("modules/universal_edges/edge/power_box") local function create_power_link(id, edge, offset, entity) - power_box.create(offset, edge, entity.surface) + power_box.create(offset, edge, entity.surface, entity) clusterio_api.send_json("universal_edges:edge_link_update", { type = "create_power_link", edge_id = id, diff --git a/module/edge/power_box.lua b/module/edge/power_box.lua index 329000b..280390d 100644 --- a/module/edge/power_box.lua +++ b/module/edge/power_box.lua @@ -1,40 +1,35 @@ local edge_util = require("modules/universal_edges/edge/util") -local eei_type = "ue_eei_tertiary" - -local function create_power_box(offset, edge, surface) +local function create_power_box(offset, edge, surface, powerpole) local edge_x = edge_util.offset_to_edge_x(offset, edge) - local eei_pos = edge_util.edge_pos_to_world({ edge_x, -1 }, edge) - local charge_sensor = surface.find_entity("accumulator", eei_pos) - local powerpole = surface.find_entity("substation", eei_pos) - local eei - if surface.entity_prototype_collides(eei_type, eei_pos, false) then - -- Is the eei already there? - eei = surface.find_entity(eei_type, eei_pos) - if not eei then + local accumulator_pos = edge_util.edge_pos_to_world({ edge_x, -1 }, edge) + local accumulator = surface.find_entity("accumulator", accumulator_pos) + local powerpole_pos = accumulator_pos + local powerpole_created = false + local link_powerpole = powerpole + if surface.entity_prototype_collides("accumulator", accumulator_pos, false) then + -- Is the accumulator already there? + if not accumulator then return false end end - if not eei then - eei = surface.create_entity { - name = eei_type, - position = eei_pos, - } + if not link_powerpole or not link_powerpole.valid then + link_powerpole = surface.find_entity("substation", powerpole_pos) end - - if not charge_sensor then - charge_sensor = surface.create_entity { - name = "accumulator", - position = eei_pos, + if not link_powerpole then + link_powerpole = surface.create_entity { + name = "substation", + position = powerpole_pos, } + powerpole_created = true end - if not powerpole then - powerpole = surface.create_entity { - name = "substation", - position = eei_pos, + if not accumulator then + accumulator = surface.create_entity { + name = "accumulator", + position = accumulator_pos, } end @@ -43,14 +38,20 @@ local function create_power_box(offset, edge, surface) end if edge.linked_power[offset] then - edge.linked_power[offset].eei = eei - edge.linked_power[offset].charge_sensor = charge_sensor - edge.linked_power[offset].powerpole = powerpole + if not powerpole_created + and edge.linked_power[offset].powerpole_created + and edge.linked_power[offset].powerpole == link_powerpole + then + powerpole_created = true + end + edge.linked_power[offset].accumulator = accumulator + edge.linked_power[offset].powerpole = link_powerpole + edge.linked_power[offset].powerpole_created = powerpole_created else edge.linked_power[offset] = { - eei = eei, - charge_sensor = charge_sensor, - powerpole = powerpole, + accumulator = accumulator, + powerpole = link_powerpole, + powerpole_created = powerpole_created, } end @@ -62,30 +63,19 @@ local function remove_power_box(offset, edge, surface) if edge.linked_power and edge.linked_power[offset] then local link = edge.linked_power[offset] - if link.eei and link.eei.valid then - link.eei.destroy() - end - if link.charge_sensor and link.charge_sensor.valid then - link.charge_sensor.destroy() + if link.accumulator and link.accumulator.valid then + link.accumulator.destroy() end - if link.powerpole and link.powerpole.valid then + if link.powerpole_created and link.powerpole and link.powerpole.valid then link.powerpole.destroy() end edge.linked_power[offset] = nil else - local eei_pos = edge_util.edge_pos_to_world({ edge_x, -1 }, edge) - local eei = surface.find_entity(eei_type, eei_pos) - if eei then - eei.destroy() - end - local charge_sensor = surface.find_entity("accumulator", eei_pos) - if charge_sensor then - charge_sensor.destroy() - end - local powerpole = surface.find_entity("substation", eei_pos) - if powerpole then - powerpole.destroy() + local accumulator_pos = edge_util.edge_pos_to_world({ edge_x, -1 }, edge) + local accumulator = surface.find_entity("accumulator", accumulator_pos) + if accumulator then + accumulator.destroy() end end end diff --git a/module/edge/power_link.lua b/module/edge/power_link.lua index 0cd6d2d..92b30cf 100644 --- a/module/edge/power_link.lua +++ b/module/edge/power_link.lua @@ -1,8 +1,21 @@ local clusterio_api = require("modules/clusterio/api") local itertools = require("modules/universal_edges/itertools") +local function get_network_charge(link) + local powerpole = link.powerpole + if not powerpole or not powerpole.valid then + return nil, nil + end + local energy = powerpole.electric_network_accumulator_energy + local capacity = powerpole.electric_network_accumulator_capacity + if energy == nil or capacity == nil then + return nil, nil + end + return energy, capacity +end + --[[ - Send our current EEI charge to our partner for balancing + Send our current network charge to our partner for balancing ]] local function poll_links(id, edge, ticks_left) if not edge.linked_power then @@ -17,15 +30,13 @@ local function poll_links(id, edge, ticks_left) for offset, link in itertools.partial_pairs( edge.linked_power, edge.linked_power_state, ticks_left ) do - if link.eei and link.eei.valid then - local local_energy = link.eei.energy - + local energy, capacity = get_network_charge(link) + if energy ~= nil and capacity ~= nil then power_transfers[#power_transfers + 1] = { offset = offset, - energy = local_energy + (link.lua_buffered_energy or 0), + energy = energy, + capacity = capacity, } - else - log("FATAL: received power for a link that does not have an eei " .. offset) end end @@ -35,87 +46,9 @@ local function poll_links(id, edge, ticks_left) power_transfers = power_transfers, }) end - - -- Add power to the eei from the lua buffer to get smooth graphs - for _, edge in pairs(storage.universal_edges.edges) do - if not edge.linked_power then - goto continue - end - for _offset, link in pairs(edge.linked_power) do - if not link then - log("FATAL: Received power for non-existant link at offset " .. link.offset) - goto continue2 - end - if not link.eei then - log("FATAL: received power for a link that does not have an eei " .. link.offset) - goto continue2 - end - if storage.universal_edges.linked_power_update_tick ~= nil and link.lua_buffered_energy ~= nil and link.lua_buffered_energy > 0 then - local ticks_until_next_frame = 5 + - math.max(0, - storage.universal_edges.linked_power_update_tick + - (storage.universal_edges.linked_power_update_period or 60) - game.tick) - link.eei.energy = link.eei.energy + link.lua_buffered_energy / ticks_until_next_frame - link.lua_buffered_energy = math.max(0, - link.lua_buffered_energy - link.lua_buffered_energy / ticks_until_next_frame) - end - ::continue2:: - end - ::continue:: - end - - -- Balance links in the same power network - local networks = {} - for _, edge in pairs(storage.universal_edges.edges) do - if not edge.linked_power then - goto continue - end - for _offset, link in pairs(edge.linked_power) do - if not link then - log("FATAL: Received power for non-existant link at offset " .. link.offset) - goto continue2 - end - if not link.eei then - log("FATAL: received power for a link that does not have an eei " .. link.offset) - goto continue2 - end - if link.eei.valid then - local network = link.eei.electric_network_id - if not networks[network] then - networks[network] = {} - end - networks[network][#networks[network] + 1] = link - end - ::continue2:: - end - ::continue:: - end - for _id, network in pairs(networks) do - local total_energy = 0 - for _, link in pairs(network) do - total_energy = total_energy + link.eei.energy + (link.lua_buffered_energy or 0) - end - local average_energy = total_energy / #network - for _, link in pairs(network) do - -- ensure electric buffer size is at least large enough to hold either the current stored - -- energy (including any lua buffer) or the computed average for the network - link.eei.electric_buffer_size = math.max(link.eei.electric_buffer_size, - link.eei.energy + (link.lua_buffered_energy or 0), average_energy) - -- Clear the Lua-side buffered energy before applying the average to avoid double-counting. - -- The total energy across the network should remain equal to total_energy, so each - -- link's eei.energy is set to the computed average and any transient lua buffer is removed. - link.lua_buffered_energy = 0 - link.eei.energy = average_energy - end - end end local function receive_transfers(edge, power_transfers) - if storage.universal_edges.linked_power_update_tick then - storage.universal_edges.linked_power_update_period = game.tick - storage.universal_edges - .linked_power_update_tick - end - storage.universal_edges.linked_power_update_tick = game.tick if power_transfers == nil then return {} end @@ -126,40 +59,45 @@ local function receive_transfers(edge, power_transfers) log("FATAL: Received power for non-existant link at offset " .. power_transfer.offset) goto continue end - if not link.eei then - log("FATAL: received power for a link that does not have an eei " .. power_transfer.offset) - goto continue - end - if power_transfer.energy then - local eei = link.eei - local remote_energy = power_transfer.energy - local local_energy = eei.energy + (link.lua_buffered_energy or 0) - local average = (remote_energy + local_energy) / 2 - local balancing_amount = math.abs(remote_energy - local_energy) / 2 + if power_transfer.energy ~= nil and power_transfer.capacity ~= nil then + local local_energy, local_capacity = get_network_charge(link) + if local_energy ~= nil and local_capacity ~= nil then + local remote_energy = power_transfer.energy + local remote_capacity = power_transfer.capacity + local total_capacity = local_capacity + remote_capacity + if total_capacity > 0 then + local target_charge = (local_energy + remote_energy) / total_capacity + local target_local_energy = target_charge * local_capacity - -- Only transfer balance in one direction - the partner will handle balancing the other way - if average > local_energy then - -- Send how much fluid we balanced as response - power_response_transfers[#power_response_transfers + 1] = { - offset = power_transfer.offset, - amount_balanced = average - local_energy, - } - -- Update internal buffer - link.lua_buffered_energy = average - eei.energy + -- Only transfer balance in one direction - the partner will handle balancing the other way + if target_local_energy > local_energy then + local requested = target_local_energy - local_energy + local powerpole = link.powerpole + if powerpole and powerpole.valid then + local max_add = requested + if local_capacity > 0 then + max_add = math.max(0, math.min(requested, local_capacity - local_energy)) + end + if max_add > 0 then + powerpole.electric_network_accumulator_energy = local_energy + max_add + power_response_transfers[#power_response_transfers + 1] = { + offset = power_transfer.offset, + amount_balanced = max_add, + } + end + end + end + end end - - -- Set dynamic buffer size - eei.electric_buffer_size = math.max(balancing_amount * 10, 1000000, local_energy) end if power_transfer.amount_balanced then - -- First pull from link.lua_buffered_energy then link.eei.energy - local energy_to_remove_from_local = math.min(power_transfer.amount_balanced, link.lua_buffered_energy) - link.lua_buffered_energy = math.max(0, link.lua_buffered_energy - energy_to_remove_from_local) - power_transfer.amount_balanced = power_transfer.amount_balanced - energy_to_remove_from_local - - -- Pull from the accumulator item - link.eei.energy = math.max(0, link.eei.energy - power_transfer.amount_balanced) + local local_energy, local_capacity = get_network_charge(link) + local powerpole = link.powerpole + if local_energy ~= nil and local_capacity ~= nil and powerpole and powerpole.valid then + local new_energy = math.max(0, local_energy - power_transfer.amount_balanced) + powerpole.electric_network_accumulator_energy = new_energy + end end ::continue:: end