Skip to content
Open
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
2 changes: 1 addition & 1 deletion controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
{ labels: ["edge_id"] }
);

async function loadDatabase(config: lib.ControllerConfig, filename: string, logger: lib.Logger): Promise<Map<string, Edge>> {

Check warning on line 23 in controller.ts

View workflow job for this annotation

GitHub Actions / Eslint

This line has a length of 125. Maximum allowed is 120
let itemsPath = path.resolve(config.get("controller.database_directory"), filename);
logger.verbose(`Loading ${itemsPath}`);
try {
Expand All @@ -38,7 +38,7 @@
}
}

async function saveDatabase(config: lib.ControllerConfig, datastore: Map<any, any>, filename: string, logger: lib.Logger) {

Check warning on line 41 in controller.ts

View workflow job for this annotation

GitHub Actions / Eslint

This line has a length of 123. Maximum allowed is 120
if (datastore) {
let file = path.resolve(config.get("controller.database_directory"), filename);
logger.verbose(`writing ${file}`);
Expand Down Expand Up @@ -151,7 +151,7 @@
}
if (["source", "target"].includes(key)) {
return (Object.keys(edge[key]) as (keyof EdgeTargetSpecification)[]).every(subKey => (
(edge[key] as EdgeTargetSpecification)[subKey] === (oldEdge[key] as EdgeTargetSpecification)[subKey]

Check warning on line 154 in controller.ts

View workflow job for this annotation

GitHub Actions / Eslint

This line has a length of 124. Maximum allowed is 120
));
}
return edge[key] === oldEdge[key];
Expand Down Expand Up @@ -263,7 +263,7 @@
// 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) => {
Expand All @@ -275,11 +275,11 @@
}
// Add targets alraedy processed by the patfhinder (distance > 1)
target.reachable_targets.forEach((value, key) => {
dest.reachable_targets.set(key, Math.min(value + 1, dest.reachable_targets.get(key) || Infinity));

Check warning on line 278 in controller.ts

View workflow job for this annotation

GitHub Actions / Eslint

This line has a length of 122. Maximum allowed is 120
});
}
});
new_solution = [...destinations.values()].map(dest => [...dest.reachable_targets.entries()].map(([key, value]) => `${key}:${value}`).join(",")).join(";");

Check warning on line 282 in controller.ts

View workflow job for this annotation

GitHub Actions / Eslint

This line has a length of 166. Maximum allowed is 120
}

// Log if we hit the iteration limit
Expand All @@ -297,7 +297,7 @@

// Send updates to instances
/*
This currently sends updates to all instances, even if they haven't changed - this is then deduplicated on the Lua side.

Check warning on line 300 in controller.ts

View workflow job for this annotation

GitHub Actions / Eslint

This line has a length of 132. Maximum allowed is 120
This is because in cases of a crash, the Lua side penalty map might not match what the server remembers.
The fix for this is to make the instance send its current penalty map on startup and request an udpate.
*/
Expand All @@ -311,7 +311,7 @@
return;
}

this.controller.sendTo({ instanceId: dest.source_instance_id }, new messages.EdgeLinkUpdate(edgeId, "update_train_penalty_map", {

Check warning on line 314 in controller.ts

View workflow job for this annotation

GitHub Actions / Eslint

This line has a length of 141. Maximum allowed is 120
offset: Number(offset),
penalty_map: Object.fromEntries(dest.reachable_targets.entries()),
}));
Expand Down
2 changes: 2 additions & 0 deletions instance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
type PowerTransfer = {
offset: number,
energy?: number,
capacity?: number,
amount_balanced?: number,
}

Expand Down Expand Up @@ -170,6 +171,7 @@
}
// 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) {
Expand Down Expand Up @@ -250,7 +252,7 @@

async onStart() {
this.logger.info("instance::onStart");
await this.sendRcon(`/sc universal_edges.set_config({instance_id = ${this.instance.config.get("instance.id")}})`);

Check warning on line 255 in instance.ts

View workflow job for this annotation

GitHub Actions / Eslint

This line has a length of 122. Maximum allowed is 120
}

async onStop() {
Expand Down Expand Up @@ -381,7 +383,7 @@
)
);
// Send response back to game
await this.sendRcon(`/sc universal_edges.teleport_player_to_server_response("${data.player_name}", "${address}")`);

Check warning on line 386 in instance.ts

View workflow job for this annotation

GitHub Actions / Eslint

This line has a length of 123. Maximum allowed is 120
}

async handleEdgeUpdate(event: messages.EdgeUpdate) {
Expand Down Expand Up @@ -422,7 +424,7 @@
edgeBuffer.edge = edge;
}
// Update ingame config
await this.sendRcon(`/sc universal_edges.edge_update("${edge.id}", '${lib.escapeString(JSON.stringify(edge))}')`);

Check warning on line 427 in instance.ts

View workflow job for this annotation

GitHub Actions / Eslint

This line has a length of 126. Maximum allowed is 120

// Update edge callbacks
let callbacks = this.edgeCallbacks.get(edge.id);
Expand Down
2 changes: 1 addition & 1 deletion module/edge/create_power_link.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
88 changes: 39 additions & 49 deletions module/edge/power_box.lua
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -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

Expand All @@ -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
Expand Down
166 changes: 52 additions & 114 deletions module/edge/power_link.lua
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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

Expand All @@ -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
Expand All @@ -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
Expand Down
Loading