1
0
mirror of https://github.com/mt-mods/biome_lib.git synced 2025-07-14 06:10:22 +02:00

1 Commits

Author SHA1 Message Date
53f55cdb4d make sure a listed block is indeed loaded before writing to it
(in case the mapgen takes long enough on a server that players
have wandered off and the server has unloaded blocks)

for each block queued, make sure it and all 6 of its neighbors are
actually loaded before populating, else move the block to the end of the
queue.
2021-04-05 12:46:06 -04:00
2 changed files with 139 additions and 133 deletions

266
init.lua
View File

@ -9,16 +9,27 @@
biome_lib = {} biome_lib = {}
biome_lib.air = {name = "air"} biome_lib.air = {name = "air"}
biome_lib.block_log = {} biome_lib.blocklist_aircheck = {}
biome_lib.blocklist_no_aircheck = {}
biome_lib.actionslist_aircheck = {} biome_lib.surface_nodes_aircheck = {}
biome_lib.actionslist_no_aircheck = {} biome_lib.surface_nodes_no_aircheck = {}
biome_lib.surfaceslist_aircheck = {} biome_lib.surfaceslist_aircheck = {}
biome_lib.surfaceslist_no_aircheck = {} biome_lib.surfaceslist_no_aircheck = {}
biome_lib.actioncount_aircheck = {}
biome_lib.actioncount_no_aircheck = {}
biome_lib.actionslist_aircheck = {}
biome_lib.actionslist_no_aircheck = {}
biome_lib.modpath = minetest.get_modpath("biome_lib") biome_lib.modpath = minetest.get_modpath("biome_lib")
biome_lib.total_no_aircheck_calls = 0
biome_lib.queue_run_ratio = tonumber(minetest.settings:get("biome_lib_queue_run_ratio")) or 100
local function tableize(s) local function tableize(s)
return string.split(string.trim(string.gsub(s, " ", ""))) return string.split(string.trim(string.gsub(s, " ", "")))
end end
@ -317,7 +328,7 @@ local function populate_single_surface(biome, pos, perlin_fertile_area, checkair
return true return true
end end
function biome_lib.populate_surfaces(biome, nodes_or_function_or_model, snodes, checkair) function biome_lib:populate_surfaces(biome, nodes_or_function_or_model, snodes, checkair)
local items_added = 0 local items_added = 0
biome_lib:set_defaults(biome) biome_lib:set_defaults(biome)
@ -381,16 +392,13 @@ function biome_lib.populate_surfaces(biome, nodes_or_function_or_model, snodes,
if objtype == "table" then if objtype == "table" then
if nodes_or_function_or_model.axiom then if nodes_or_function_or_model.axiom then
biome_lib:generate_tree(p_top, nodes_or_function_or_model) biome_lib:generate_tree(p_top, nodes_or_function_or_model)
biome_lib:dbg("An L-tree was spawned at "..minetest.pos_to_string(p_top))
spawned = true spawned = true
else else
local fdir = nil local fdir = nil
if biome.random_facedir then if biome.random_facedir then
fdir = math.random(biome.random_facedir[1], biome.random_facedir[2]) fdir = math.random(biome.random_facedir[1], biome.random_facedir[2])
end end
local n=nodes_or_function_or_model[math.random(#nodes_or_function_or_model)] minetest.swap_node(p_top, { name = nodes_or_function_or_model[math.random(#nodes_or_function_or_model)], param2 = fdir })
minetest.swap_node(p_top, { name = n, param2 = fdir })
biome_lib:dbg("Node \""..n.."\" was randomly picked from a list and placed at "..minetest.pos_to_string(p_top))
spawned = true spawned = true
end end
elseif objtype == "string" and elseif objtype == "string" and
@ -400,16 +408,13 @@ function biome_lib.populate_surfaces(biome, nodes_or_function_or_model, snodes,
fdir = math.random(biome.random_facedir[1], biome.random_facedir[2]) fdir = math.random(biome.random_facedir[1], biome.random_facedir[2])
end end
minetest.swap_node(p_top, { name = nodes_or_function_or_model, param2 = fdir }) minetest.swap_node(p_top, { name = nodes_or_function_or_model, param2 = fdir })
biome_lib:dbg("Node \""..nodes_or_function_or_model.."\" was placed at "..minetest.pos_to_string(p_top))
spawned = true spawned = true
elseif objtype == "function" then elseif objtype == "function" then
nodes_or_function_or_model(pos) nodes_or_function_or_model(pos)
biome_lib:dbg("A function was run on surface node at "..minetest.pos_to_string(pos))
spawned = true spawned = true
elseif objtype == "string" and pcall(loadstring(("return %s(...)"): elseif objtype == "string" and pcall(loadstring(("return %s(...)"):
format(nodes_or_function_or_model)),pos) then format(nodes_or_function_or_model)),pos) then
spawned = true spawned = true
biome_lib:dbg("An obsolete string-specified function was run on surface node at "..minetest.pos_to_string(p_top))
else else
biome_lib:dbg("Warning: Ignored invalid definition for object "..dump(nodes_or_function_or_model).." that was pointed at {"..dump(pos).."}") biome_lib:dbg("Warning: Ignored invalid definition for object "..dump(nodes_or_function_or_model).." that was pointed at {"..dump(pos).."}")
end end
@ -422,130 +427,121 @@ function biome_lib.populate_surfaces(biome, nodes_or_function_or_model, snodes,
return items_added return items_added
end end
-- Primary log read-out/mapgen spawner -- make sure the target block and all 6 neighbors are actually loaded.
local function confirm_block_surroundings(p) local function confirm_block_surroundings(pos)
local n=minetest.get_node_or_nil(p) return minetest.get_node_or_nil(pos)
if not n or n.name == "ignore" then return false end and minetest.get_node_or_nil({ x=pos.x-16, y=pos.y, z=pos.z })
and minetest.get_node_or_nil({ x=pos.x+16, y=pos.y, z=pos.z })
for x = -32,32,64 do -- step of 64 causes it to only check the 8 corner blocks and minetest.get_node_or_nil({ x=pos.x, y=pos.y-16, z=pos.z })
for y = -32,32,64 do and minetest.get_node_or_nil({ x=pos.x, y=pos.y+16, z=pos.z })
for z = -32,32,64 do and minetest.get_node_or_nil({ x=pos.x, y=pos.y, z=pos.z-16 })
local n=minetest.get_node_or_nil({x=p.x + x, y=p.y + y, z=p.z + z}) and minetest.get_node_or_nil({ x=pos.x, y=pos.y, z=pos.z+16 })
if not n or n.name == "ignore" then return false end
end
end
end
return true
end end
biome_lib.block_recheck_list = {} -- Primary mapgen spawner, for mods that can work with air checking enabled on
biome_lib.run_block_recheck_list = false -- a surface during the initial map read stage.
function biome_lib.generate_block(shutting_down) function biome_lib:generate_block_with_air_checking(shutdown)
if not biome_lib.blocklist_aircheck[1] then
if shutting_down then return
if #biome_lib.block_recheck_list > 0 then
for i = 1, #biome_lib.block_recheck_list do
biome_lib.block_log[#biome_lib.block_log + 1] = biome_lib.block_recheck_list[i]
end
biome_lib.block_recheck_list = {}
end
biome_lib.run_block_recheck_list = false
else
if biome_lib.run_block_recheck_list
and not biome_lib.block_recheck_list[1] then
biome_lib.run_block_recheck_list = false
end
end end
local blocklog = biome_lib.run_block_recheck_list local minp = biome_lib.blocklist_aircheck[1][1]
and biome_lib.block_recheck_list local maxp = biome_lib.blocklist_aircheck[1][2]
or biome_lib.block_log
if not blocklog[1] then return end -- use the block hash as a unique key into the surface nodes
-- tables, so that we can write the tables thread-safely.
local minp = blocklog[1][1] local blockhash = minetest.hash_node_position(minp)
local maxp = blocklog[1][2]
local airflag = blocklog[1][3]
local pos_hash = minetest.hash_node_position(minp)
if not biome_lib.pos_hash then -- we need to read the maplock and get the surfaces list if not biome_lib.surface_nodes_aircheck.blockhash then -- read it into the block cache
biome_lib.pos_hash = {} biome_lib.surface_nodes_aircheck.blockhash =
if not confirm_block_surroundings(minp) minetest.find_nodes_in_area_under_air(minp, maxp, biome_lib.surfaceslist_aircheck)
and not shutting_down then -- if any neighbors appear not to be loaded, skip this block for now biome_lib.actioncount_aircheck.blockhash = 1
if #biome_lib.surface_nodes_aircheck.blockhash > 0 then
if biome_lib.run_block_recheck_list then biome_lib:dbg("Mapblock at "..minetest.pos_to_string(minp).." added, with "..#biome_lib.surface_nodes_aircheck.blockhash.." surface nodes detected.")
biome_lib.block_log[#biome_lib.block_log + 1] = table.copy(biome_lib.block_recheck_list[1])
table.remove(biome_lib.block_recheck_list, 1)
else
biome_lib.block_recheck_list[#biome_lib.block_recheck_list + 1] = table.copy(biome_lib.block_log[1])
table.remove(biome_lib.block_log, 1)
end
biome_lib.pos_hash = nil
biome_lib:dbg("Mapblock at "..minetest.pos_to_string(minp)..
" had a neighbor not fully emerged, skipped it for now.")
return
else
biome_lib.pos_hash.surface_node_list = airflag
and minetest.find_nodes_in_area_under_air(minp, maxp, biome_lib.surfaceslist_aircheck)
or minetest.find_nodes_in_area(minp, maxp, biome_lib.surfaceslist_no_aircheck)
biome_lib.pos_hash.action_index = 1
if #biome_lib.pos_hash.surface_node_list > 0 then
biome_lib:dbg("Mapblock at "..minetest.pos_to_string(minp)..
" has "..#biome_lib.pos_hash.surface_node_list..
" surface nodes to work on (airflag="..dump(airflag)..")")
end
end end
elseif not (airflag and biome_lib.actionslist_aircheck[biome_lib.pos_hash.action_index]) elseif not shutdown and not confirm_block_surroundings(minp) and #biome_lib.blocklist_aircheck > 1 then
and not (not airflag and biome_lib.actionslist_no_aircheck[biome_lib.pos_hash.action_index]) then biome_lib.blocklist_aircheck[#biome_lib.blocklist_aircheck+1] = biome_lib.blocklist_aircheck[1]
-- the block is finished, remove it table.remove(biome_lib.blocklist_aircheck, 1)
if #biome_lib.pos_hash.surface_node_list > 0 then biome_lib:dbg("Mapblock at "..minetest.pos_to_string(minp).." had an unloaded neighbor, moved it to the end of the queue.")
biome_lib:dbg("Deleted mapblock "..minetest.pos_to_string(minp).." from the block log")
end
table.remove(blocklog, 1)
biome_lib.pos_hash = nil
else else
-- below, [1] is biome, [2] is the thing to be added if biome_lib.actionslist_aircheck[biome_lib.actioncount_aircheck.blockhash] then
local added = 0 -- [1] is biome, [2] is node/function/model
if airflag then local added = biome_lib:populate_surfaces(
if biome_lib.actionslist_aircheck[biome_lib.pos_hash.action_index] then biome_lib.actionslist_aircheck[biome_lib.actioncount_aircheck.blockhash][1],
added = biome_lib.populate_surfaces( biome_lib.actionslist_aircheck[biome_lib.actioncount_aircheck.blockhash][2],
biome_lib.actionslist_aircheck[biome_lib.pos_hash.action_index][1], biome_lib.surface_nodes_aircheck.blockhash, true)
biome_lib.actionslist_aircheck[biome_lib.pos_hash.action_index][2], if added > 0 then
biome_lib.pos_hash.surface_node_list, true) biome_lib:dbg("Ran biome_lib:populate_surfaces for block at "..minetest.pos_to_string(minp)..
biome_lib.pos_hash.action_index = biome_lib.pos_hash.action_index + 1 ". Entry #"..biome_lib.actioncount_aircheck.blockhash.." added "..added.." items.")
end end
biome_lib.actioncount_aircheck.blockhash = biome_lib.actioncount_aircheck.blockhash + 1
else else
if biome_lib.actionslist_no_aircheck[biome_lib.pos_hash.action_index] then table.remove(biome_lib.blocklist_aircheck, 1)
added = biome_lib.populate_surfaces( biome_lib.surface_nodes_aircheck.blockhash = nil
biome_lib.actionslist_no_aircheck[biome_lib.pos_hash.action_index][1], biome_lib.actioncount_aircheck.blockhash = nil
biome_lib.actionslist_no_aircheck[biome_lib.pos_hash.action_index][2],
biome_lib.pos_hash.surface_node_list, false)
biome_lib.pos_hash.action_index = biome_lib.pos_hash.action_index + 1
end
end end
if added > 0 then end
biome_lib:dbg("biome_lib.populate_surfaces ran on mapblock at ".. end
minetest.pos_to_string(minp)..". Entry #"..
(biome_lib.pos_hash.action_index-1).." added "..added.." items.") -- Secondary mapgen spawner, for mods that require disabling of
-- checking for air during the initial map read stage.
function biome_lib:generate_block_no_aircheck(shutdown)
if not biome_lib.blocklist_no_aircheck[1] then
return
end
local minp = biome_lib.blocklist_no_aircheck[1][1]
local maxp = biome_lib.blocklist_no_aircheck[1][2]
local blockhash = minetest.hash_node_position(minp)
if not biome_lib.surface_nodes_no_aircheck.blockhash then
biome_lib.surface_nodes_no_aircheck.blockhash =
minetest.find_nodes_in_area(minp, maxp, biome_lib.surfaceslist_no_aircheck)
biome_lib.actioncount_no_aircheck.blockhash = 1
elseif not shutdown and not confirm_block_surroundings(minp) and #biome_lib.blocklist_no_aircheck > 1 then
biome_lib.blocklist_no_aircheck[#biome_lib.blocklist_no_aircheck+1] = biome_lib.blocklist_no_aircheck[1]
table.remove(biome_lib.blocklist_no_aircheck, 1)
else
if biome_lib.actionslist_no_aircheck[biome_lib.actioncount_no_aircheck.blockhash] then
biome_lib:populate_surfaces(
biome_lib.actionslist_no_aircheck[biome_lib.actioncount_no_aircheck.blockhash][1],
biome_lib.actionslist_no_aircheck[biome_lib.actioncount_no_aircheck.blockhash][2],
biome_lib.surface_nodes_no_aircheck.blockhash, false)
biome_lib.actioncount_no_aircheck.blockhash = biome_lib.actioncount_no_aircheck.blockhash + 1
else
table.remove(biome_lib.blocklist_no_aircheck, 1)
biome_lib.surface_nodes_no_aircheck.blockhash = nil
biome_lib.actioncount_no_aircheck.blockhash = nil
end end
end end
end end
-- "Play" them back, populating them with new stuff in the process -- "Play" them back, populating them with new stuff in the process
local rr = tonumber(minetest.settings:get("biome_lib_queue_run_ratio")) or -100 local step_duration = tonumber(minetest.settings:get("dedicated_server_step"))
biome_lib.queue_run_ratio = 100 - rr
biome_lib.entries_per_step = math.max(-rr, 1)
minetest.register_globalstep(function(dtime) minetest.register_globalstep(function(dtime)
if not biome_lib.block_log[1] then return end -- the block log is empty if dtime >= step_duration + 0.1 -- don't attempt to populate if lag is already too high
or math.random(100) > biome_lib.queue_run_ratio
or (#biome_lib.blocklist_aircheck == 0 and #biome_lib.blocklist_no_aircheck == 0) then
return
end
if math.random(100) > biome_lib.queue_run_ratio then return end biome_lib.globalstep_start_time = minetest.get_us_time()
for s = 1, biome_lib.entries_per_step do biome_lib.globalstep_runtime = 0
biome_lib.generate_block() while (#biome_lib.blocklist_aircheck > 0 or #biome_lib.blocklist_no_aircheck > 0)
and biome_lib.globalstep_runtime < 200000 do -- 0.2 seconds, in uS.
if #biome_lib.blocklist_aircheck > 0 then
biome_lib:generate_block_with_air_checking(false)
end
if #biome_lib.blocklist_no_aircheck > 0 then
biome_lib:generate_block_no_aircheck(false)
end
biome_lib.globalstep_runtime = minetest.get_us_time() - biome_lib.globalstep_start_time
end end
end) end)
@ -553,21 +549,26 @@ end)
-- to prevent unpopulated map areas -- to prevent unpopulated map areas
minetest.register_on_shutdown(function() minetest.register_on_shutdown(function()
if #biome_lib.block_log + #biome_lib.block_recheck_list == 0 then if #biome_lib.blocklist_aircheck == 0 then
return return
end end
print("[biome_lib] Stand by, playing out the rest of the mapblock log") print("[biome_lib] Stand by, playing out the rest of the aircheck mapblock log")
print("(there are "..(#biome_lib.block_log + #biome_lib.block_recheck_list).." entries)...") print("(there are "..#biome_lib.blocklist_aircheck.." entries)...")
while #biome_lib.block_log > 0 do while #biome_lib.blocklist_aircheck > 0 do
biome_lib.generate_block(true) biome_lib:generate_block_with_air_checking(true)
end
end)
minetest.register_on_shutdown(function()
if #biome_lib.blocklist_aircheck == 0 then
return
end end
if #biome_lib.block_recheck_list > 0 then print("[biome_lib] Stand by, playing out the rest of the no-aircheck mapblock log")
biome_lib.block_log = table.copy(biome_lib.block_recheck_list) print("(there are "..#biome_lib.blocklist_no_aircheck.." entries)...")
while #biome_lib.block_log > 0 do while #biome_lib.blocklist_no_aircheck > 0 do
biome_lib.generate_block(true) biome_lib:generate_block_no_aircheck(true)
end
end end
end) end)
@ -762,17 +763,22 @@ function biome_lib:get_nodedef_field(nodename, fieldname)
end end
if DEBUG then if DEBUG then
biome_lib.last_count = 0 biome_lib.last_count_air = 0
biome_lib.last_count_no_air = 0
function biome_lib.show_pending_block_count() function biome_lib.show_pending_block_counts()
if biome_lib.last_count ~= #biome_lib.block_log then if biome_lib.last_count_air ~= #biome_lib.blocklist_aircheck
biome_lib:dbg("Pending block count: "..(#biome_lib.block_log + #biome_lib.block_recheck_list)) or biome_lib.last_count_no_air ~= #biome_lib.blocklist_no_aircheck then
biome_lib.last_count = #biome_lib.block_log biome_lib:dbg(string.format("Pending block counts, air: %-7i no-air: %i",
#biome_lib.blocklist_aircheck, #biome_lib.blocklist_no_aircheck))
biome_lib.last_count_air = #biome_lib.blocklist_aircheck
biome_lib.last_count_no_air = #biome_lib.blocklist_no_aircheck
end end
minetest.after(1, biome_lib.show_pending_block_count) minetest.after(1, biome_lib.show_pending_block_counts)
end end
biome_lib.show_pending_block_count() biome_lib.show_pending_block_counts()
minetest.after(0, function() minetest.after(0, function()
print("Registered a total of "..(#biome_lib.surfaceslist_aircheck)+(#biome_lib.surfaceslist_no_aircheck).." surface types to be evaluated, spread") print("Registered a total of "..(#biome_lib.surfaceslist_aircheck)+(#biome_lib.surfaceslist_no_aircheck).." surface types to be evaluated, spread")

View File

@ -65,10 +65,10 @@ minetest.register_on_generated(function(minp, maxp, blockseed)
local bmin = {x=minx, y=miny, z=minz} local bmin = {x=minx, y=miny, z=minz}
local bmax = {x=minx + 15, y=miny + 15, z=minz + 15} local bmax = {x=minx + 15, y=miny + 15, z=minz + 15}
biome_lib.block_log[#biome_lib.block_log + 1] = { bmin, bmax, true }
biome_lib.block_log[#biome_lib.block_log + 1] = { bmin, bmax, false } biome_lib.blocklist_aircheck[#biome_lib.blocklist_aircheck + 1] = { bmin, bmax }
biome_lib.blocklist_no_aircheck[#biome_lib.blocklist_no_aircheck + 1] = { bmin, bmax }
end end
end end
end end
biome_lib.run_block_recheck_list = true
end) end)