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:
parent
f7d712543f
commit
3c10f0e970
|
@ -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:
|
||||||
|
|
Loading…
Reference in New Issue