Skip to content

Technic compatibility with fully contained network move capability #79

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 7 commits into
base: master
Choose a base branch
from
Draft
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
4 changes: 2 additions & 2 deletions .luacheckrc
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ globals = {
-- write
"travelnet",
"pipeworks",
"beds"
"beds",
"technic"
}

read_globals = {
Expand All @@ -28,7 +29,6 @@ read_globals = {
"digilines",
"mesecons",
"mesecon",
"technic",
"locator",
"display_api",
"areas",
Expand Down
5 changes: 5 additions & 0 deletions compat/compat.lua
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ local has_areas_mod = minetest.get_modpath("areas")
local has_drawers_mod = minetest.get_modpath("drawers")
local has_textline_mod = minetest.get_modpath("textline")

dofile(MP.."/compat/technic_networks.lua")

if minetest.get_modpath("travelnet") then
dofile(MP.."/compat/travelnet.lua")
end
Expand Down Expand Up @@ -85,4 +87,7 @@ jumpdrive.target_region_compat = function(source_pos1, source_pos2, target_pos1,
jumpdrive.beds_compat(target_pos1, target_pos2, delta_vector)
end

if has_technic_mod then
jumpdrive.technic_network_compat(source_pos1, source_pos2, target_pos1, delta_vector)
end
end
128 changes: 128 additions & 0 deletions compat/technic_networks.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@

local vecadd = vector.add
local poshash = minetest.hash_node_position
local get_node = minetest.get_node
local cables = technic and technic.cables
local machines = technic and technic.machine_tiers

-- Check for technic mod compatibility, compatible with technic plus
if not technic or not technic.remove_network or not technic.networks or not cables or not machines then
minetest.log("warning", "[jumpdrive] Incompatible technic mod loaded")
jumpdrive.technic_network_compat = function() end
return
end

-- Function to move technic network to another position
local network_node_arrays = {"PR_nodes","BA_nodes","RE_nodes"}
local function move_network(net, delta)
for _,tblname in ipairs(network_node_arrays) do
local tbl = net[tblname]
for i=#tbl,1,-1 do
tbl[i] = vecadd(tbl[i], delta)
end
end
local new_net_id = poshash(vecadd(technic.network2pos(net.id), delta))
local new_all_nodes = {}
local all_nodes = net.all_nodes
for old_node_id, old_pos in pairs(all_nodes) do
local new_pos = vecadd(old_pos, delta)
local node_id = poshash(new_pos)
cables[old_node_id] = nil
cables[node_id] = new_net_id
new_all_nodes[node_id] = new_pos
end
net.all_nodes = new_all_nodes
technic.networks[net.id] = nil
net.id = new_net_id
technic.networks[net.id] = net
end

local function vecadd2(a, b, c)
return {
x = a.x + b.x + c.x,
y = a.y + b.y + c.y,
z = a.z + b.z + c.z,
}
end

local function add_edge_cable_net(pos, size, delta, t)
-- Find connected positions outside edge of box from given position
-- NOTE: At source position first found would be enough but not at target
local x = pos.x == 0 and -1 or (pos.x == size.x and 1)
local y = pos.y == 0 and -1 or (pos.y == size.y and 1)
local z = pos.z == 0 and -1 or (pos.z == size.z and 1)
if x then
local v = vecadd2(pos, {x=x,y=0,z=0}, delta)
local net_id = cables[poshash(v)]
if net_id then t[net_id] = true end
end
if y then
local v = vecadd2(pos, {x=0,y=y,z=0}, delta)
local net_id = cables[poshash(v)]
if net_id then t[net_id] = true end
end
if z then
local v = vecadd2(pos, {x=0,y=0,z=z}, delta)
local net_id = cables[poshash(v)]
if net_id then t[net_id] = true end
end
return x, y, z
end

jumpdrive.technic_network_compat = function(source_pos1, source_pos2, target_pos1, delta_vector)

-- search results
local jump_networks = {} -- jumped fully contained networks
local edge_networks = {} -- networks crossing jumped area edge

-- search for networks in area
local size = vector.apply(vector.subtract(source_pos1, source_pos2),math.abs)
for x=0,size.x do
for y=0,size.y do
for z=0,size.z do
local local_pos = {x=x,y=y,z=z}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Problem here is when large areas are jumped, this most probably is not even near most efficient method to check nodes in area.
Any suggestions for better way?

Copy link
Member Author

@S-S-X S-S-X Oct 25, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be better to find_nodes_in_area and include cable groups and machine group, if that is significantly faster way to get nodes then loop here could be a lot shorter especially for large areas.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

uhm, this does not sound really efficient, would that be another use-case in that #47 could help with. It would need to be defined on every cable-definition (and possibly on the switching station):

minetest.register_node("my:node", {
 on_movenode = function(from_pos, to_pos)
 end
})

This way the nested for-loops could be replaced with some logic within the jumpdrive mod (one nested for-loop is better than 2 of them, right?).

Let me know if something like that would help and if the parameters are enough (from- and to-position) i think i have some time to implement this and rewrite the jump-compatibility functions (or even move it to the external mods)

Copy link
Member Author

@S-S-X S-S-X Oct 25, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, thing is with R15 it is still well over 50% faster than rebuilding network. I think mostly because it is very rare to read any nodes from world (only around edges of box when there is also active network at location) but other than that it is pure math.
But also can clearly see how performance degrades when area gets bigger, that is because of looping over all locations.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This way the nested for-loops could be replaced with some logic within the jumpdrive mod (one nested for-loop is better than 2 of them, right?).

Node move cannot be used alone, did you notice that this loop also checks taget and source around jumped area for possible network connections at target and cable cutting at source.
It could be used if there's information about location in jumped area so that connections across edge can be checked.

But it would work yes with few additions and possibly better than blindly looping through whole area.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would provide a way to react on single nodes and to process/finalize the whole after a jump:

local jumped_cables = {}

minetest.register_node("technic:cable_xy", {
 on_movenode = function(from, to, additional_info)
  -- TODO: collect cable-positions here
  table.insert(jumped_cables, { from=from, to=to, edge=additional_info.edge })
 end
})

-- callback for jump finalization stage (for example savefile writes)
jumpdrive.register_after_jump(function(from_area, to_area)
  -- from_area = { pos1, pos2 }
  -- to_area = { pos1, pos2 }

  -- TODO: finalize (somehow)
  for _, entry in ipairs(jumped_cables) do
    -- magic
  end

  -- reset list for next jump
  jumped_cables = {}
end)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so additional_info.edge would contain 1-3 edge directions / vector representing directions, that would work yes.
Most of logic from this PR could be moved to finalization and if edge provides vector for 90 degree directions relative to edge wall then that could also be skipped in node move implementation for networks.

I think that could be useful for some other things too that can depend on nearby stuff outside of source or target area.

Copy link
Member Author

@S-S-X S-S-X Oct 25, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

btw, tried with following. Removed nested loops and used 254 nodes instead of 29791 nodes.
those nested loops were about 8 times faster...

        local nodes = minetest.find_nodes_in_area(source_pos1, source_pos2, {
                "group:technic_machine",
                "group:technic_lv_cable",
                "group:technic_mv_cable",
                "group:technic_hv_cable",
        })

        -- search results
        local jump_networks = {} -- jumped fully contained networks
        local edge_networks = {} -- networks crossing jumped area edge

        -- search for networks in area
        local size = vector.apply(vector.subtract(source_pos1, source_pos2),math.abs)
        --for x=0,size.x do
                --for y=0,size.y do
                        --for z=0,size.z do
        for _, pos in ipairs(nodes) do
....

actually after testing more hoping to get caches warmed those nested loops seem to be over 10 times faster on average
tested this with R15

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

additional_info.edge would contain 1-3 edge directions / vector representing directions, that would work yes.

Hmm, it does not (yet) only a boolean value if it is on the edge 😋
But i guess returning a vector for the edge-position should be fairly easy, something along the lines of this:

-- only an edge to the lower/y direction
edge = { x=0, y=-1, z=0 }
-- on an upper corner
egde = { x=1, y=1, z=-1 }

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could put up some simple test for this and if it still seems that nested loops using just pure math is better then maybe good option would be to implement actual network moving in technic mod instead and just call some technic.move_network(source1, source2, target1) from here.
But seems that needs some testing, and possibility to register move callback for nodes will be useful no matter how network data moving will be actually implemented.

local src_pos = vecadd(source_pos1, local_pos)
local net_id = cables[poshash(src_pos)]
if net_id then
-- Add to (dirty) jumped fully contained networks
jump_networks[net_id] = true
end
if x==0 or x==size.x or y==0 or y==size.y or z==0 or z==size.z then
-- Candidate for border crossing network, check neighbors across border
if net_id then
-- Inside network is active, check outside for active and inactive
local dir_x, dir_y, dir_z = add_edge_cable_net(local_pos, size, source_pos1, edge_networks)
if dir_x then
local name = get_node(vecadd2(local_pos, target_pos1, {x=dir_x,y=0,z=0})).name
if machines[name] or technic.get_cable_tier(name) then edge_networks[net_id] = true end
end
if dir_y then
local name = get_node(vecadd2(local_pos, target_pos1, {x=0,y=dir_y,z=0})).name
if machines[name] or technic.get_cable_tier(name) then edge_networks[net_id] = true end
end
if dir_z then
local name = get_node(vecadd2(local_pos, target_pos1, {x=0,y=0,z=dir_z})).name
if machines[name] or technic.get_cable_tier(name) then edge_networks[net_id] = true end
end
else
-- Inside is inactive, check outside for active
add_edge_cable_net(local_pos, size, target_pos1, edge_networks)
end
end
end
end
end
for net_id, _ in pairs(edge_networks) do
-- Remove edge networks to create contained networks list
jump_networks[net_id] = nil
-- And clean up network crossing jumped area edges
technic.remove_network(net_id)
end
for net_id, _ in pairs(jump_networks) do
-- Move fully contained networks with jumpdrive
local net = technic.networks[net_id]
if net then
move_network(net, delta_vector)
end
end
end