Aaron Suen b6232f4f99 Fix freeze due to door infinite loop
A certain arrangement of door-gears with a couple of
pieces of sand next to it can get in a situation that
causes an infinite loop, e.g. as if the sand nodes are
trying to switch places with one another and form a
circular dependency.

The simplest patch for it is just to limit recursion
for now.  Randomness in the depth reduces the risk
that machines themselves can get stuck in some
pathological state.
2022-02-26 15:16:32 -05:00

81 lines
2.0 KiB
Lua

-- LUALOCALS < ---------------------------------------------------------
local ipairs, math, minetest, nodecore, vector
= ipairs, math, minetest, nodecore, vector
local math_random
= math.random
-- LUALOCALS > ---------------------------------------------------------
local hashpos = minetest.pos_to_string
local is_falling = {groups = {falling_node = true}}
local queue = {}
local queued = {}
function nodecore.door_push(pos, ...)
local key = hashpos(pos)
if queued[key] then return end
local node = minetest.get_node(pos)
if not nodecore.match(node, is_falling) then return end
node.param = nil
local try = {}
for i, dir in ipairs({...}) do
try[i] = vector.add(pos, dir)
end
queue[#queue + 1] = {
key = key,
from = pos,
try = try,
attempts = math_random(10, 20)
}
queued[key] = true
end
local function tryprocess(item, retry)
local node = minetest.get_node(item.from)
if not nodecore.match(node, is_falling) then return end
for _, t in ipairs(item.try) do
if nodecore.buildable_to(t) then
local meta = minetest.get_meta(item.from):to_table()
minetest.remove_node(item.from)
nodecore.fallcheck({x = item.from.x, y = item.from.y + 1, z = item.from.z})
nodecore.set_loud(t, node)
meta.fields = meta.fields or {}
meta.fields.tweenfrom = minetest.serialize(item.from)
minetest.get_meta(t):from_table(meta)
nodecore.visinv_update_ents(t)
nodecore.fallcheck(t)
local re = retry[hashpos(item.from)]
if not re then return end
for _, r in ipairs(re) do
if r.attempts and r.attempts > 0 and not queued[r.key] then
r.attempts = r.attempts - 1
queue[#queue + 1] = r
queued[r.key] = true
end
end
end
end
for _, t in ipairs(item.try) do
local key = hashpos(t)
local r = retry[key]
if not r then
r = {}
retry[key] = r
end
r[#r + 1] = item
end
end
minetest.register_globalstep(function()
local retry = {}
local i = 1
while i <= #queue do
local item = queue[i]
queued[hashpos(item.from)] = nil
tryprocess(item, retry)
i = i + 1
end
queue = {}
queued = {}
end)