From cf675474555a940bc7835a979fb59238f0fad7c0 Mon Sep 17 00:00:00 2001 From: Jude Melton-Houghton Date: Thu, 14 Apr 2022 13:34:28 -0400 Subject: [PATCH] Combine mapgen callbacks for efficiency (#7) 1. Two read/write pairs to the VM have been replaced with one. 2. If no changes are made to the data, it is not written back to the VM. --- init.lua | 142 ++++++++++++++++++++++++++++--------------------------- 1 file changed, 73 insertions(+), 69 deletions(-) diff --git a/init.lua b/init.lua index 1c7b0eb..09e2ab9 100644 --- a/init.lua +++ b/init.lua @@ -320,29 +320,7 @@ if not springs then end minetest.register_node("dynamic_liquid:clay", clay_def) -local data = {} - if springs then - local c_clay = minetest.get_content_id("default:clay") - local c_spring_clay = minetest.get_content_id("dynamic_liquid:clay") - - -- Turn mapgen clay into spring clay - minetest.register_on_generated(function(minp, maxp, seed) - if minp.y >= water_level or maxp.y <= -15 then - return - end - local vm, emin, emax = minetest.get_mapgen_object("voxelmanip") - vm:get_data(data) - - for voxelpos, voxeldata in pairs(data) do - if voxeldata == c_clay then - data[voxelpos] = c_spring_clay - end - end - vm:set_data(data) - vm:write_to_map() - end) - minetest.register_abm({ label = "dynamic_liquid damp clay spring", nodenames = {"dynamic_liquid:clay"}, @@ -409,34 +387,38 @@ end local mapgen_prefill = minetest.settings:get_bool("dynamic_liquid_mapgen_prefill", true) -local waternodes +if springs or mapgen_prefill then + local data = {} -if mapgen_prefill then + local c_clay = minetest.get_content_id("default:clay") + local c_spring_clay = minetest.get_content_id("dynamic_liquid:clay") local c_water = minetest.get_content_id("default:water_source") local c_air = minetest.get_content_id("air") - waternodes = {} + local waternodes = {} local fill_to = function (vi, data, area) if area:containsi(vi) and area:position(vi).y <= water_level then if data[vi] == c_air then data[vi] = c_water table.insert(waternodes, vi) + return true end end + return false end -- local count = 0 local drop_liquid = function(vi, data, area, min_y) if data[vi] ~= c_water then -- we only care about water. - return + return false end local start = vi -- remember the water node we started from local ystride = area.ystride vi = vi - ystride if data[vi] ~= c_air then -- if there's no air below this water node, give up immediately. - return + return false end vi = vi - ystride -- There's air below the water, so move down one. while data[vi] == c_air and area:position(vi).y > min_y do @@ -452,58 +434,80 @@ if mapgen_prefill then -- if count % 100 == 0 then -- minetest.chat_send_all("dropped water " .. (start-vi)/ystride .. " at " .. minetest.pos_to_string(area:position(vi))) -- end + return true end minetest.register_on_generated(function(minp, maxp, seed) - if minp.y > water_level then - -- we're in the sky. - return - end - - local vm, emin, emax = minetest.get_mapgen_object("voxelmanip") - local area = VoxelArea:new{MinEdge=emin, MaxEdge=emax} - vm:get_data(data) local maxp_y = maxp.y local minp_y = minp.y - - if maxp_y > -70 then - local top = vector.new(maxp.x, math.min(maxp_y, water_level), maxp.z) -- prevents flood fill from affecting any water above sea level - for vi in area:iterp(minp, top) do - if data[vi] == c_water then - table.insert(waternodes, vi) + local vm, emin, emax + local dirty = false + local update_liquids = false + + if springs and minp_y < water_level and maxp_y > -15 then + vm, emin, emax = minetest.get_mapgen_object("voxelmanip") + vm:get_data(data) + + for voxelpos = 1, #data do + if data[voxelpos] == c_clay then + data[voxelpos] = c_spring_clay + dirty = true end end - - while table.getn(waternodes) > 0 do - local vi = table.remove(waternodes) - local below = vi - area.ystride - local left = vi - area.zstride - local right = vi + area.zstride - local front = vi - 1 - local back = vi + 1 - - fill_to(below, data, area) - fill_to(left, data, area) - fill_to(right, data, area) - fill_to(front, data, area) - fill_to(back, data, area) + end + + if mapgen_prefill and minp_y <= water_level then + if not vm then + vm, emin, emax = minetest.get_mapgen_object("voxelmanip") + vm:get_data(data) end - else - -- Caves sometimes generate with liquid nodes hovering in mid air. - -- This immediately drops them straight down as far as they can go, reducing the ABM thrashing. - -- We only iterate down to minp.y+1 because anything at minp.y will never be dropped farther anyway. - for vi in area:iter(minp.x, minp_y+1, minp.z, maxp.x, maxp_y, maxp.z) do - -- fortunately, area:iter iterates through y columns going upward. Just what we need! - -- We could possibly be a bit more efficient by remembering how far we dropped then - -- last liquid node in a column and moving stuff down that far, - -- but for now let's keep it simple. - drop_liquid(vi, data, area, minp_y) + local area = VoxelArea:new{MinEdge=emin, MaxEdge=emax} + + if maxp_y > -70 then + local top = vector.new(maxp.x, math.min(maxp_y, water_level), maxp.z) -- prevents flood fill from affecting any water above sea level + for vi in area:iterp(minp, top) do + if data[vi] == c_water then + table.insert(waternodes, vi) + end + end + + while table.getn(waternodes) > 0 do + local vi = table.remove(waternodes) + local below = vi - area.ystride + local left = vi - area.zstride + local right = vi + area.zstride + local front = vi - 1 + local back = vi + 1 + + dirty = fill_to(below, data, area) or dirty + dirty = fill_to(left, data, area) or dirty + dirty = fill_to(right, data, area) or dirty + dirty = fill_to(front, data, area) or dirty + dirty = fill_to(back, data, area) or dirty + end + else + -- Caves sometimes generate with liquid nodes hovering in mid air. + -- This immediately drops them straight down as far as they can go, reducing the ABM thrashing. + -- We only iterate down to minp.y+1 because anything at minp.y will never be dropped farther anyway. + for vi in area:iter(minp.x, minp_y+1, minp.z, maxp.x, maxp_y, maxp.z) do + -- fortunately, area:iter iterates through y columns going upward. Just what we need! + -- We could possibly be a bit more efficient by remembering how far we dropped then + -- last liquid node in a column and moving stuff down that far, + -- but for now let's keep it simple. + dirty = drop_liquid(vi, data, area, minp_y) or dirty + end + end + + update_liquids = dirty + end + + if dirty then + vm:set_data(data) + vm:write_to_map() + if update_liquids then + vm:update_liquids() end end - - vm:set_data(data) - vm:write_to_map() - vm:update_liquids() end) end