diff --git a/class_layout.lua b/class_layout.lua index cf9a7bb..7c263a6 100644 --- a/class_layout.lua +++ b/class_layout.lua @@ -18,18 +18,12 @@ local get_node_image = function(pos, node) -- Record what kind of thing we've got in a builder node so its facing can be rotated properly if minetest.get_item_group(node.name, "digtron") == 4 then - -- https://github.com/minetest-mods/digtron/issues/17 had a user encounter a crash here, - -- adding logging to hopefully catch it if it happens again. - if node_image.meta.inventory.main ~= nil then - local build_item = node_image.meta.inventory.main[1] - if build_item ~= "" then - local build_item_def = minetest.registered_nodes[ItemStack(build_item):get_name()] - if build_item_def ~= nil then - node_image.build_item_paramtype2 = build_item_def.paramtype2 - end + local build_item = node_image.meta.inventory.main[1] + if build_item ~= "" then + local build_item_def = minetest.registered_nodes[ItemStack(build_item):get_name()] + if build_item_def ~= nil then + node_image.build_item_paramtype2 = build_item_def.paramtype2 end - else - minetest.log("error", string.format("Digtron node in group 4 lacks a 'main' inventory. Please update the issue at https://github.com/minetest-mods/digtron/issues/17. Node image: %s", dump(node_image))) end end return node_image @@ -327,43 +321,12 @@ function DigtronLayout.can_write_layout_image(self) return true end -function DigtronLayout.write_layout_image(self, player) +-- We need to call on_dignode and on_placenode for dug and placed nodes, +-- but that triggers falling nodes (sand and whatnot) and destroys Digtrons +-- if done during mid-write. So we need to defer the calls until after the +-- Digtron has been fully written. - -- We need to call on_dignode and on_placenode for dug and placed nodes, - -- but that triggers falling nodes (sand and whatnot) and destroys Digtrons - -- if done during mid-write. So we need to defer the calls until after the - -- Digtron has been fully written. - - local dug_nodes = {} - local placed_nodes = {} - - -- fake_player will be passed to callbacks to prevent actual player from "taking the blame" for this action. - -- For example, the hunger mod shouldn't be making the player hungry when he moves Digtron. - digtron.fake_player:update(self.controller, player:get_player_name()) - -- note that the actual player is still passed to the per-node after_place_node and after_dig_node, should they exist. - - -- destroy the old digtron - local oldpos, _ = self.old_pos_pointset:pop() - while oldpos ~= nil do - local old_node = minetest.get_node(oldpos) - local old_meta = minetest.get_meta(oldpos) - minetest.remove_node(oldpos) - table.insert(dug_nodes, {oldpos, old_node, old_meta}) - oldpos, _ = self.old_pos_pointset:pop() - end - - -- create the new one - for k, node_image in pairs(self.all) do - local new_pos = node_image.pos - local new_node = node_image.node - local old_node = minetest.get_node(new_pos) - minetest.set_node(new_pos, new_node) - minetest.get_meta(new_pos):from_table(node_image.meta) - - table.insert(placed_nodes, {new_pos, new_node, old_node}) - end - - +local node_callbacks = function(dug_nodes, placed_nodes, player) for _, dug_node in pairs(dug_nodes) do local old_pos = dug_node[1] local old_node = dug_node[2] @@ -403,10 +366,71 @@ function DigtronLayout.write_layout_image(self, player) if new_def ~= nil and new_def.after_place_node ~= nil then new_def.after_place_node(new_pos, player) end - end end +local set_node_with_retry = function(pos, node) + local start_time = minetest.get_us_time() + while not minetest.set_node(pos, node) do + if minetest.get_us_time() - start_time > 1000000 then -- 1 second in useconds + return false + end + end + return true +end + +local set_meta_with_retry = function(meta, meta_table) + local start_time = minetest.get_us_time() + while not meta:from_table(meta_table) do + if minetest.get_us_time() - start_time > 1000000 then -- 1 second in useconds + return false + end + end + return true +end + +function DigtronLayout.write_layout_image(self, player) + local dug_nodes = {} + local placed_nodes = {} + + -- destroy the old digtron + local oldpos, _ = self.old_pos_pointset:pop() + while oldpos ~= nil do + local old_node = minetest.get_node(oldpos) + local old_meta = minetest.get_meta(oldpos) + + if not set_node_with_retry(oldpos, {name="air"}) then + minetest.log("error", "DigtronLayout.write_layout_image failed to destroy old Digtron node, aborting write.") + return false + end + + table.insert(dug_nodes, {oldpos, old_node, old_meta}) + oldpos, _ = self.old_pos_pointset:pop() + end + + -- create the new one + for k, node_image in pairs(self.all) do + local new_pos = node_image.pos + local new_node = node_image.node + local old_node = minetest.get_node(new_pos) + + if not (set_node_with_retry(new_pos, new_node) and set_meta_with_retry(minetest.get_meta(new_pos), node_image.meta)) then + minetest.log("error", "DigtronLayout.write_layout_image failed to write a Digtron node, aborting write.") + return false + end + + table.insert(placed_nodes, {new_pos, new_node, old_node}) + end + + -- fake_player will be passed to callbacks to prevent actual player from "taking the blame" for this action. + -- For example, the hunger mod shouldn't be making the player hungry when he moves Digtron. + digtron.fake_player:update(self.controller, player:get_player_name()) + -- note that the actual player is still passed to the per-node after_place_node and after_dig_node, should they exist. + node_callbacks(dug_nodes, placed_nodes, player) + + return true +end + --------------------------------------------------------------------------------------------- -- Serialization. Currently only serializes the data that is needed by the crate, upgrade this function if more is needed diff --git a/nodes/node_axle.lua b/nodes/node_axle.lua index 99dda4b..bdfaf76 100644 --- a/nodes/node_axle.lua +++ b/nodes/node_axle.lua @@ -45,13 +45,15 @@ minetest.register_node("digtron:axle", { local image = DigtronLayout.create(pos, clicker) image:rotate_layout_image(node.param2) if image:can_write_layout_image() then - image:write_layout_image(clicker) - - minetest.sound_play("whirr", {gain=1.0, pos=pos}) - meta = minetest.get_meta(pos) - meta:set_string("waiting", "true") - meta:set_string("infotext", nil) - minetest.get_node_timer(pos):start(digtron.config.cycle_time*2) + if image:write_layout_image(clicker) then + minetest.sound_play("whirr", {gain=1.0, pos=pos}) + meta = minetest.get_meta(pos) + meta:set_string("waiting", "true") + meta:set_string("infotext", nil) + minetest.get_node_timer(pos):start(digtron.config.cycle_time*2) + else + meta:set_string("infotext", "unrecoverable write_layout_image error") + end else minetest.sound_play("buzzer", {gain=1.0, pos=pos}) meta:set_string("infotext", S("Digtron is obstructed.")) diff --git a/util_execute_cycle.lua b/util_execute_cycle.lua index 3997395..b294aa2 100644 --- a/util_execute_cycle.lua +++ b/util_execute_cycle.lua @@ -283,7 +283,9 @@ digtron.execute_dig_cycle = function(pos, clicker) --move the array layout:move_layout_image(dir) - layout:write_layout_image(clicker) + if not layout:write_layout_image(clicker) then + return pos, "unrecoverable write_layout_image error", 1 + end local oldpos = {x=pos.x, y=pos.y, z=pos.z} pos = vector.add(pos, dir) meta = minetest.get_meta(pos) @@ -412,7 +414,10 @@ digtron.execute_move_cycle = function(pos, clicker) minetest.sound_play("truck", {gain=1.0, pos=pos}) --move the array - layout:write_layout_image(clicker) + if not layout:write_layout_image(clicker) then + return pos, "unrecoverable write_layout_image error", 1 + end + pos = vector.add(pos, dir) if move_player then clicker:moveto(vector.add(clicker:getpos(), dir), true) @@ -520,7 +525,9 @@ digtron.execute_downward_dig_cycle = function(pos, clicker) --move the array layout:move_layout_image(digtron.facedir_to_down_dir(facing)) - layout:write_layout_image(clicker) + if not layout:write_layout_image(clicker) then + return pos, "unrecoverable write_layout_image error", 1 + end local oldpos = {x=pos.x, y=pos.y, z=pos.z} pos = vector.add(pos, dir) meta = minetest.get_meta(pos)