Rewrite portal removal to avoid stack overflow

This solves a problem were nether portal removal would trigger deep
recursion through node callbacks.  For large portals this could result
in stack overflow crashes on LuaJIT.  The issue is solved by rewriting
the portal removal to avoid recursion and removing the portal in one
operation using minetest.bulk_set_node.
This commit is contained in:
Elias Åström 2022-02-25 22:58:36 +01:00 committed by Gitea
parent f7d712543f
commit 3c10f0e970

View File

@ -204,37 +204,52 @@ local function get_target(p)
end end
end end
-- Destroy portal if pos (portal frame or portal node) got destroyed -- Destroy a nether portal. Connected portal nodes are searched and removed
-- using 'bulk_set_node'. This function is called from 'after_destruct' of
-- nether portal nodes. The flag 'destroying_portal' is used to avoid this
-- function being called recursively through callbacks in 'bulk_set_node'.
local destroying_portal = false
local function destroy_nether_portal(pos, node) local function destroy_nether_portal(pos, node)
if not node then return end if destroying_portal then
local nn, orientation = node.name, node.param2
local obsidian = nn == OBSIDIAN
local function check_remove(pos, orientation)
local node = get_node(pos)
if node and (node.name == PORTAL and (orientation == nil or (node.param2 == orientation))) then
minetest.remove_node(pos)
remove_exit(pos)
end
end
if obsidian then -- check each of 6 sides of it and destroy every portal:
check_remove({x = pos.x - 1, y = pos.y, z = pos.z}, 0)
check_remove({x = pos.x + 1, y = pos.y, z = pos.z}, 0)
check_remove({x = pos.x, y = pos.y, z = pos.z - 1}, 1)
check_remove({x = pos.x, y = pos.y, z = pos.z + 1}, 1)
check_remove({x = pos.x, y = pos.y - 1, z = pos.z})
check_remove({x = pos.x, y = pos.y + 1, z = pos.z})
return return
end end
if orientation == 0 then destroying_portal = true
check_remove({x = pos.x - 1, y = pos.y, z = pos.z}, 0)
check_remove({x = pos.x + 1, y = pos.y, z = pos.z}, 0) local orientation = node.param2
else local checked_tab = { [minetest.hash_node_position(pos)] = true }
check_remove({x = pos.x, y = pos.y, z = pos.z - 1}, 1) local nodes = { pos }
check_remove({x = pos.x, y = pos.y, z = pos.z + 1}, 1)
local function check_remove(pos)
local h = minetest.hash_node_position(pos)
if checked_tab[h] then
return
end
local node = minetest.get_node(pos)
if node and node.name == PORTAL and (orientation == nil or node.param2 == orientation) then
table.insert(nodes, pos)
checked_tab[h] = true
end
end end
check_remove({x = pos.x, y = pos.y - 1, z = pos.z})
check_remove({x = pos.x, y = pos.y + 1, z = pos.z}) local i = 1
while i <= #nodes do
pos = nodes[i]
if orientation == 0 then
check_remove({x = pos.x - 1, y = pos.y, z = pos.z})
check_remove({x = pos.x + 1, y = pos.y, z = pos.z})
else
check_remove({x = pos.x, y = pos.y, z = pos.z - 1})
check_remove({x = pos.x, y = pos.y, z = pos.z + 1})
end
check_remove({x = pos.x, y = pos.y - 1, z = pos.z})
check_remove({x = pos.x, y = pos.y + 1, z = pos.z})
remove_exit(pos)
i = i + 1
end
minetest.bulk_set_node(nodes, { name = "air" })
destroying_portal = false
end end
local on_rotate local on_rotate
@ -817,7 +832,23 @@ local usagehelp = S("To open a Nether portal, place an upright frame of obsidian
minetest.override_item(OBSIDIAN, { minetest.override_item(OBSIDIAN, {
_doc_items_longdesc = longdesc, _doc_items_longdesc = longdesc,
_doc_items_usagehelp = usagehelp, _doc_items_usagehelp = usagehelp,
after_destruct = destroy_nether_portal, after_destruct = function(pos, node)
local function check_remove(pos, orientation)
local node = get_node(pos)
if node and node.name == PORTAL then
minetest.remove_node(pos)
end
end
-- check each of 6 sides of it and destroy every portal
check_remove({x = pos.x - 1, y = pos.y, z = pos.z})
check_remove({x = pos.x + 1, y = pos.y, z = pos.z})
check_remove({x = pos.x, y = pos.y, z = pos.z - 1})
check_remove({x = pos.x, y = pos.y, z = pos.z + 1})
check_remove({x = pos.x, y = pos.y - 1, z = pos.z})
check_remove({x = pos.x, y = pos.y + 1, z = pos.z})
end,
_on_ignite = function(user, pointed_thing) _on_ignite = function(user, pointed_thing)
local x, y, z = pointed_thing.under.x, pointed_thing.under.y, pointed_thing.under.z local x, y, z = pointed_thing.under.x, pointed_thing.under.y, pointed_thing.under.z
-- Check empty spaces around obsidian and light all frames found: -- Check empty spaces around obsidian and light all frames found: