From e9b162b326ea54e4b73fbe46959d2f397eb77506 Mon Sep 17 00:00:00 2001 From: Sun Serega Date: Wed, 12 Jun 2024 20:50:15 +0200 Subject: [PATCH 1/2] Smarter wire network reconstruction Remote interfaces allow custom packed combinators to be `nil`, but wires going past them were deleted This commit adds new rewiring system, to connect things that have non-`nil` packed representation with an equivalent (yet different and simpler than unpacked version) wire network --- scripts/build.lua | 333 +++++++++++++++++++++++++++++++++++----------- 1 file changed, 257 insertions(+), 76 deletions(-) diff --git a/scripts/build.lua b/scripts/build.lua index dad2ae6..24a0801 100644 --- a/scripts/build.lua +++ b/scripts/build.lua @@ -568,10 +568,92 @@ function build.create_packed_circuit_internal(procinfo, nolamp, recursionSet, local bp_entities = bp.get_blueprint_entities() if bp_entities then - ---@type table - local inner_processors = {} - for index, bpentity in pairs(bp_entities) do + ---@alias WireType defines.wire_type + ---@alias ConnPointName any + ---@alias BpWireNetwork { wire_color: string, wire: defines.wire_type, entities: { bp_index: integer, conn_point_name: ConnPointName }[] } + + ---@type BpWireNetwork[] + local wire_networks = {} + ---@type table>> + local bpentity_network_map = {} + ---@param network BpWireNetwork + ---@param conn_point_name ConnPointName + ---@param bp_index integer + ---@param skip_network_conflict boolean + ---@return boolean + local function add_bpentity_conn(network, conn_point_name, bp_index, skip_network_conflict) + bpentity_network_map[bp_index] = bpentity_network_map[bp_index] or {} + bpentity_network_map[bp_index][network.wire] = bpentity_network_map[bp_index][network.wire] or {} + if bpentity_network_map[bp_index][network.wire][conn_point_name] then + if network ~= bpentity_network_map[bp_index][network.wire][conn_point_name] and not skip_network_conflict then + error("Conflicting wire networks") + end + return false + end + bpentity_network_map[bp_index][network.wire][conn_point_name] = network + + network.entities = network.entities or {} + table.insert(network.entities, { + bp_index = bp_index, + conn_point_name = conn_point_name, + }) + + local links = bp_entities[bp_index].connections[conn_point_name][network.wire_color] + for _, link in pairs(links) do + local circuit_id = link.circuit_id + if circuit_id == nil then + -- If there is only one connection, circuit_id would be nil + -- In that case, find the name of the connection manually: + for target_conn_point_name, _ in pairs(bp_entities[link.entity_id].connections) do + if circuit_id ~= nil then + error("Expected only one connection point") + end + circuit_id = target_conn_point_name + end + if circuit_id == nil then + error("No connection point found") + end + end + add_bpentity_conn(network, circuit_id .. "", link.entity_id, false) + end + + return true + end + + for bp_index, bpentity in pairs(bp_entities) do + if bpentity.connections then + for conn_point_name, wire_colors in pairs(bpentity.connections) do + for wire_color, _ in pairs(wire_colors) do + ---@type WireType + local wire = defines.wire_type[wire_color] + if wire ~= defines.wire_type.copper then + ---@type BpWireNetwork + local network = { wire_color = wire_color, wire = wire } + if add_bpentity_conn(network, conn_point_name, bp_index, true) then + table.insert(wire_networks, network) + end + end + end + end + end + end + + --TODO Would be great to rewrite this to use a system similar to remote_name_map for all entities, uncluding standard ones + -- Things like regular power-polls can now just return nil as their packed version + -- And combinators with unconfigured output can do so too - that would also optimized out all the were connections, dependant on that combinator + -- Personally, I've decided on at least waiting for the "Speaker support" to be merged - otherwise there will be ugly conflicts with such refactoring + ---@type table + local processed_bp_entities = {} + ---@param bp_index integer + ---@return boolean + local function add_bpentity(bp_index) + if processed_bp_entities[bp_index] then + return index_map[bp_index] ~= nil + end + processed_bp_entities[bp_index] = true + local bpentity = bp_entities[bp_index] + local name = bpentity.name local packed_name = allowed_name_map[name] @@ -600,7 +682,7 @@ function build.create_packed_circuit_internal(procinfo, nolamp, recursionSet, } ---@cast entity -nil table.insert(entities, entity) - index_map[index] = #entities + index_map[bp_index] = #entities if name == "constant-combinator" then local cb = entity.get_or_create_control_behavior() --[[@as LuaConstantCombinatorControlBehavior]] @@ -697,16 +779,6 @@ function build.create_packed_circuit_internal(procinfo, nolamp, recursionSet, end end end - elseif name == iopoint_name then - if tags then - local proc_index = tags.proc_index - local proc = inner_processors[proc_index] - if not proc then - proc = { iopoints = {} } - inner_processors[proc_index] = proc - end - proc.iopoints[tags.iopoint_index] = entity - end elseif name == display_name then if tags and not tags.is_internal then display.start(tags, entity) @@ -723,32 +795,9 @@ function build.create_packed_circuit_internal(procinfo, nolamp, recursionSet, } table.insert(input_list, input_prop) end - elseif name == commons.processor_name or name == - commons.processor_name_1x1 then - local proc = inner_processors[index] - if not proc then - proc = { iopoints = {} } - inner_processors[index] = proc - end - proc.name = name - if tags then - proc.blueprint = tags.blueprint --[[@as string]] - proc.tick = tags.tick --[[@as integer]] - proc.model = tags.model --[[@as string]] - proc.label = tags.label --[[@as string]] - end - local value_id = (tags and tags.value_id) or tools.get_id() - proc.inner_input = { - x = position.x + bpentity.position.x / 32, - y = position.y + bpentity.position.y / 32, - value_id = value_id --[[@as string]], - inner_inputs = {}, - label = proc.label or proc.model - } - table.insert(input_list, proc.inner_input) end elseif remote_name_map[name] then - local tags = bp.get_blueprint_entity_tags(index) + local tags = bp.get_blueprint_entity_tags(bp_index) local remote_driver = remote_name_map[name] local pos = { x = position.x + bpentity.position.x / 32, @@ -759,55 +808,187 @@ function build.create_packed_circuit_internal(procinfo, nolamp, recursionSet, surface, pos, force) if entity then table.insert(entities, entity) - index_map[index] = #entities + index_map[bp_index] = #entities end end + + return index_map[bp_index] ~= nil end - for index, bpentity in pairs(bp_entities) do - local dst_index = index_map[index] - local entity = entities[dst_index] - if entity then - if bpentity.connections then - for connection_name, colors in pairs(bpentity.connections) do - for color, links in pairs(colors) do - for _, link in pairs(links) do - local src_circuit_id = - tonumber(connection_name) or 0 - local target_circuit_id = link.circuit_id - local wire = defines.wire_type[color] - local target_index = link.entity_id - if index > target_index or - (index == target_index and src_circuit_id > - target_circuit_id) then - local target = - entities[index_map[target_index]] - if target and - (wire == defines.wire_type.red or wire == - defines.wire_type.green) then - local success = - entity.connect_neighbour { - source_circuit_id = src_circuit_id, - wire = wire, - target_entity = target, - target_circuit_id = target_circuit_id - } - if not success then - debug( - "Failed to connect: " .. index .. - " to " .. link.entity_id .. - "(" .. - tools.get_constant_name( - wire, defines.wire_type) .. - ")") - end + ---@type table + local inner_processors = {} -- Indexed by bp_index + ---@type table> + local nested_proc_bpentities = {} -- Values are bp_index=>iopoint_index maps + --TODO This table needs to be pre-filled to properly take care of at least the outer sub-processor connections + -- Tho the question of handling connections within sub-processors is open + for bp_index, bpentity in pairs(bp_entities) do + local name = bpentity.name + local tags = bpentity.tags + if name == commons.processor_name or name == commons.processor_name_1x1 then + if inner_processors[bp_index] then + error("inner processor index collision") + end + proc = { iopoints = {} } + inner_processors[bp_index] = proc + nested_proc_bpentities[proc] = {} + proc.name = name + if tags then + proc.blueprint = tags.blueprint --[[@as string]] + proc.tick = tags.tick --[[@as integer]] + proc.model = tags.model --[[@as string]] + proc.label = tags.label --[[@as string]] + end + local value_id = (tags and tags.value_id) or tools.get_id() + proc.inner_input = { + x = position.x + bpentity.position.x / 32, + y = position.y + bpentity.position.y / 32, + value_id = value_id --[[@as string]], + inner_inputs = {}, + label = proc.label or proc.model + } + table.insert(input_list, proc.inner_input) + end + end + for bp_index, bpentity in pairs(bp_entities) do + local name = bpentity.name + local tags = bpentity.tags + if name == iopoint_name then + local entity = surface.create_entity { + name = allowed_name_map[name], + position = position, + direction = bpentity.direction, + force = force + } + ---@cast entity -nil + table.insert(entities, entity) + index_map[bp_index] = #entities + + if not tags then + debug(name .. " without tags") -- Same check below is commented out + else + local proc_bp_index = tags.proc_index + local proc = inner_processors[proc_bp_index] + if not proc then + proc = { iopoints = {} } + inner_processors[proc_bp_index] = proc + end + + proc.iopoints[tags.iopoint_index] = entity + nested_proc_bpentities[proc][bp_index] = tags.iopoint_index + end + + end + end + + ---@type table + local packed_networks = {} + ---@type table + local packed_inner_processors = {} + ---@param network BpWireNetwork + local function add_packed_network(network) + if packed_networks[network] then return end + packed_networks[network] = true + + --TODO This currently doesn't handle nested processors... + -- If nested processor's IO point is connected - mark the processor and ALL of it's IO points are connected + for _, e in pairs(network.entities) do + if bp_entities[e.bp_index].name == iopoint_name then + if not bp_entities[e.bp_index].tags then + -- debug(iopoint_name .. " without tags") -- Same check above + else + local proc_bp_index = bp_entities[e.bp_index].tags.proc_index + local proc = inner_processors[proc_bp_index] + packed_inner_processors[proc_bp_index] = true + for io_bp_index, _ in pairs(nested_proc_bpentities[proc]) do + if bpentity_network_map[io_bp_index] then + for _, e_networks in pairs(bpentity_network_map[io_bp_index]) do + for _, ref_network in pairs(e_networks) do + add_packed_network(ref_network) end end end end end + --TODO also check that e.conn_point_name is not a combinator output (defines.circuit_connector_id.combinator_output) + -- Maybe add remote interface to check that for remote combinators? + -- Actually no, this is not enough. A clock connected only to output poll is valid and should be packed + -- So, instead - the algorithm needs to be 2 pass: Input=>Output, then Output=>Input + -- Otherwise other outputs of that clock would get picked up, even if they are not connected to any IO polls + -- But anyway, for this to properly work - connections within the nested processors need to be accounted for in the same way + elseif add_bpentity(e.bp_index) then + for _, e_networks in pairs(bpentity_network_map[e.bp_index]) do + for _, ref_network in pairs(e_networks) do + add_packed_network(ref_network) + end + end + end + end + + end + for _, network in pairs(wire_networks) do + local has_io = false + for _, e in pairs(network.entities) do + if bp_entities[e.bp_index].name == internal_iopoint_name then + has_io = true + break end end + if has_io then + add_packed_network(network) + end + end + + for bp_index, bpentity in pairs(bp_entities) do + if not packed_inner_processors[bp_index] and not bpentity_network_map[bp_index] and bpentity.name ~= iopoint_name then + debug(bp_entities[bp_index].name .. " entity was not packed, because it was not connected to any wire network") + end + end + + for _, network in pairs(wire_networks) do + if not packed_networks[network] then + local m = "Network with "..network.wire_color.." wire had no path to/from IO. Entities:" + local any_packed = false + for _, e in pairs(network.entities) do + if add_bpentity(e.bp_index) then + any_packed = true + end + m = m .. " " .. bp_entities[e.bp_index].name + end + if any_packed then + debug(m) + end + end + end + + for network, _ in pairs(packed_networks) do + local first_bp_entity = nil + + for _, e in pairs(network.entities) do + local pack_entity = entities[index_map[e.bp_index]] + if pack_entity then + if not first_bp_entity then + first_bp_entity = e + else + local success = pack_entity.connect_neighbour { + wire = network.wire, + target_entity = entities[index_map[first_bp_entity.bp_index]], + source_circuit_id = e.conn_point_name, + target_circuit_id = first_bp_entity.conn_point_name, + } + if not success then + debug( + "Failed to connect: " .. e.bp_index .. " (" .. bp_entities[e.bp_index].name .. ")" .. + " to " .. first_bp_entity.bp_index .. " (" .. bp_entities[first_bp_entity.bp_index].name .. ")" .. + " using " .. network.wire_color .. " wire" + ) + end + end + end + end + + if not first_bp_entity then + error("first_bp_entity not found") + end end for _, proc in pairs(inner_processors) do From 81aa7a4f7b9bf142c082d570aeae20474d5e5beb Mon Sep 17 00:00:00 2001 From: Sun Serega Date: Thu, 13 Jun 2024 13:12:00 +0200 Subject: [PATCH 2/2] cleanup --- scripts/build.lua | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/scripts/build.lua b/scripts/build.lua index 24a0801..0ba22be 100644 --- a/scripts/build.lua +++ b/scripts/build.lua @@ -819,8 +819,6 @@ function build.create_packed_circuit_internal(procinfo, nolamp, recursionSet, local inner_processors = {} -- Indexed by bp_index ---@type table> local nested_proc_bpentities = {} -- Values are bp_index=>iopoint_index maps - --TODO This table needs to be pre-filled to properly take care of at least the outer sub-processor connections - -- Tho the question of handling connections within sub-processors is open for bp_index, bpentity in pairs(bp_entities) do local name = bpentity.name local tags = bpentity.tags @@ -889,8 +887,6 @@ function build.create_packed_circuit_internal(procinfo, nolamp, recursionSet, if packed_networks[network] then return end packed_networks[network] = true - --TODO This currently doesn't handle nested processors... - -- If nested processor's IO point is connected - mark the processor and ALL of it's IO points are connected for _, e in pairs(network.entities) do if bp_entities[e.bp_index].name == iopoint_name then if not bp_entities[e.bp_index].tags then @@ -915,6 +911,7 @@ function build.create_packed_circuit_internal(procinfo, nolamp, recursionSet, -- So, instead - the algorithm needs to be 2 pass: Input=>Output, then Output=>Input -- Otherwise other outputs of that clock would get picked up, even if they are not connected to any IO polls -- But anyway, for this to properly work - connections within the nested processors need to be accounted for in the same way + -- And then don't warn when parts of nested processors are optimized out... elseif add_bpentity(e.bp_index) then for _, e_networks in pairs(bpentity_network_map[e.bp_index]) do for _, ref_network in pairs(e_networks) do