1
0
mirror of https://github.com/mt-mods/biome_lib.git synced 2025-06-29 14:50:49 +02:00

9 Commits

Author SHA1 Message Date
531577afcf rather than copy the re-check log back into the block log when it comes
time to run through it, just use it in-place, copying its entries back
to the main block log if they have to be skipped again (essentially
using the re-check list and the end of the block log as a double buffer)
2021-04-07 01:44:31 -04:00
e346fd599f rather than rearranging the block log to deal with blocks that can't be
populated yet, move those blocks to a separate list, and replay that
list whenever new mapblocks come in.  That way the main block list can
run dry and allow the block populate routines to go idle.

Thanks to Warr1024 for the idea!
2021-04-06 16:09:50 -04:00
212024a9b4 tiny optimization 2021-04-06 13:38:43 -04:00
1d2593f022 if the block list is empty, don't try to start another batch of actions. 2021-04-06 13:08:43 -04:00
3bc8737e2d make sure the target mapblock and all 8 of its corner neighbors have
been generated before populating the block (else move the target block
the end of the queue)

Thanks to Warr1024 for this idea!
2021-04-06 12:47:05 -04:00
0ea4cb3848 don't bother checking dtime during globalstep
it's enough to rely in the run ratio.
2021-04-06 10:00:16 -04:00
bef0a0d87e oops, forgot to allow for no-air-check blocks 2021-04-06 04:19:57 -04:00
89ca62e492 added a bunch of debugging info 2021-04-06 03:20:14 -04:00
a325c2ccd8 rewrote block queue handlers to reduce code duplication
and improve lag management

minetest.conf settings added:

biome_lib_dtime_limit:

Maximum lag allowed, in seconds.  Default 0.5s.  Larger values allow for
more lag, but make map updates run a bit faster.

biome_lib_queue_run_ratio:

If positive, this is the approximate number of globalstep ticks to skip
between map updates. If negative, it becomes the positive number of map
updates to run per globalstep tick before lag is checked.  No minimum
value, maximum +100.  Default: -100 (that is, 100 updates per globalstep
tick, with none intentionally skipped).  Use positive numbers for slow
machines or biome_lib-using mods that tend to cause lag, and negative
values for fast machines and mods.
2021-04-06 03:20:12 -04:00
2 changed files with 136 additions and 142 deletions

282
init.lua
View File

@ -9,27 +9,16 @@
biome_lib = {} biome_lib = {}
biome_lib.air = {name = "air"} biome_lib.air = {name = "air"}
biome_lib.blocklist_aircheck = {} biome_lib.block_log = {}
biome_lib.blocklist_no_aircheck = {}
biome_lib.surface_nodes_aircheck = {}
biome_lib.surface_nodes_no_aircheck = {}
biome_lib.surfaceslist_aircheck = {}
biome_lib.surfaceslist_no_aircheck = {}
biome_lib.actioncount_aircheck = {}
biome_lib.actioncount_no_aircheck = {}
biome_lib.actionslist_aircheck = {} biome_lib.actionslist_aircheck = {}
biome_lib.actionslist_no_aircheck = {} biome_lib.actionslist_no_aircheck = {}
biome_lib.surfaceslist_aircheck = {}
biome_lib.surfaceslist_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
@ -328,7 +317,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)
@ -392,13 +381,16 @@ 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
minetest.swap_node(p_top, { name = nodes_or_function_or_model[math.random(#nodes_or_function_or_model)], param2 = fdir }) local n=nodes_or_function_or_model[math.random(#nodes_or_function_or_model)]
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
@ -408,13 +400,16 @@ 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
@ -427,121 +422,130 @@ function biome_lib:populate_surfaces(biome, nodes_or_function_or_model, snodes,
return items_added return items_added
end end
-- make sure the target block and all 6 neighbors are actually loaded. -- Primary log read-out/mapgen spawner
local function confirm_block_surroundings(pos) local function confirm_block_surroundings(p)
return minetest.get_node_or_nil(pos) local n=minetest.get_node_or_nil(p)
and minetest.get_node_or_nil({ x=pos.x-16, y=pos.y, z=pos.z }) 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, y=pos.y-16, 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, z=pos.z-16 }) 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})
if not n or n.name == "ignore" then return false end
end
end
end
return true
end end
-- Primary mapgen spawner, for mods that can work with air checking enabled on biome_lib.block_recheck_list = {}
-- a surface during the initial map read stage. biome_lib.run_block_recheck_list = false
function biome_lib:generate_block_with_air_checking(shutdown) function biome_lib.generate_block(shutting_down)
if not biome_lib.blocklist_aircheck[1] then
return if shutting_down then
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 end
biome_lib.block_recheck_list = {}
local minp = biome_lib.blocklist_aircheck[1][1]
local maxp = biome_lib.blocklist_aircheck[1][2]
-- use the block hash as a unique key into the surface nodes
-- tables, so that we can write the tables thread-safely.
local blockhash = minetest.hash_node_position(minp)
if not biome_lib.surface_nodes_aircheck.blockhash then -- read it into the block cache
biome_lib.surface_nodes_aircheck.blockhash =
minetest.find_nodes_in_area_under_air(minp, maxp, biome_lib.surfaceslist_aircheck)
biome_lib.actioncount_aircheck.blockhash = 1
if #biome_lib.surface_nodes_aircheck.blockhash > 0 then
biome_lib:dbg("Mapblock at "..minetest.pos_to_string(minp).." added, with "..#biome_lib.surface_nodes_aircheck.blockhash.." surface nodes detected.")
end end
elseif not shutdown and not confirm_block_surroundings(minp) and #biome_lib.blocklist_aircheck > 1 then biome_lib.run_block_recheck_list = false
biome_lib.blocklist_aircheck[#biome_lib.blocklist_aircheck+1] = biome_lib.blocklist_aircheck[1]
table.remove(biome_lib.blocklist_aircheck, 1)
biome_lib:dbg("Mapblock at "..minetest.pos_to_string(minp).." had an unloaded neighbor, moved it to the end of the queue.")
else else
if biome_lib.actionslist_aircheck[biome_lib.actioncount_aircheck.blockhash] then if biome_lib.run_block_recheck_list
-- [1] is biome, [2] is node/function/model and not biome_lib.block_recheck_list[1] then
local added = biome_lib:populate_surfaces( biome_lib.run_block_recheck_list = false
biome_lib.actionslist_aircheck[biome_lib.actioncount_aircheck.blockhash][1], end
biome_lib.actionslist_aircheck[biome_lib.actioncount_aircheck.blockhash][2], end
biome_lib.surface_nodes_aircheck.blockhash, true)
local blocklog = biome_lib.run_block_recheck_list
and biome_lib.block_recheck_list
or biome_lib.block_log
if not blocklog[1] then return end
local minp = blocklog[1][1]
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
biome_lib.pos_hash = {}
if not confirm_block_surroundings(minp)
and not shutting_down then -- if any neighbors appear not to be loaded, skip this block for now
if biome_lib.run_block_recheck_list then
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
elseif not (airflag and biome_lib.actionslist_aircheck[biome_lib.pos_hash.action_index])
and not (not airflag and biome_lib.actionslist_no_aircheck[biome_lib.pos_hash.action_index]) then
-- the block is finished, remove it
if #biome_lib.pos_hash.surface_node_list > 0 then
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
-- below, [1] is biome, [2] is the thing to be added
local added = 0
if airflag then
if biome_lib.actionslist_aircheck[biome_lib.pos_hash.action_index] then
added = biome_lib.populate_surfaces(
biome_lib.actionslist_aircheck[biome_lib.pos_hash.action_index][1],
biome_lib.actionslist_aircheck[biome_lib.pos_hash.action_index][2],
biome_lib.pos_hash.surface_node_list, true)
biome_lib.pos_hash.action_index = biome_lib.pos_hash.action_index + 1
end
else
if biome_lib.actionslist_no_aircheck[biome_lib.pos_hash.action_index] then
added = biome_lib.populate_surfaces(
biome_lib.actionslist_no_aircheck[biome_lib.pos_hash.action_index][1],
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
if added > 0 then if added > 0 then
biome_lib:dbg("Ran biome_lib:populate_surfaces for block at "..minetest.pos_to_string(minp).. biome_lib:dbg("biome_lib.populate_surfaces ran on mapblock at "..
". Entry #"..biome_lib.actioncount_aircheck.blockhash.." added "..added.." items.") minetest.pos_to_string(minp)..". Entry #"..
end (biome_lib.pos_hash.action_index-1).." added "..added.." items.")
biome_lib.actioncount_aircheck.blockhash = biome_lib.actioncount_aircheck.blockhash + 1
else
table.remove(biome_lib.blocklist_aircheck, 1)
biome_lib.surface_nodes_aircheck.blockhash = nil
biome_lib.actioncount_aircheck.blockhash = nil
end
end
end
-- 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 step_duration = tonumber(minetest.settings:get("dedicated_server_step")) local rr = tonumber(minetest.settings:get("biome_lib_queue_run_ratio")) or -100
minetest.register_globalstep(function(dtime)
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
biome_lib.globalstep_start_time = minetest.get_us_time() biome_lib.queue_run_ratio = 100 - rr
biome_lib.globalstep_runtime = 0 biome_lib.entries_per_step = math.max(-rr, 1)
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. minetest.register_globalstep(function(dtime)
if #biome_lib.blocklist_aircheck > 0 then if not biome_lib.block_log[1] then return end -- the block log is empty
biome_lib:generate_block_with_air_checking(false)
end if math.random(100) > biome_lib.queue_run_ratio then return end
if #biome_lib.blocklist_no_aircheck > 0 then for s = 1, biome_lib.entries_per_step do
biome_lib:generate_block_no_aircheck(false) biome_lib.generate_block()
end
biome_lib.globalstep_runtime = minetest.get_us_time() - biome_lib.globalstep_start_time
end end
end) end)
@ -549,26 +553,21 @@ end)
-- to prevent unpopulated map areas -- to prevent unpopulated map areas
minetest.register_on_shutdown(function() minetest.register_on_shutdown(function()
if #biome_lib.blocklist_aircheck == 0 then if #biome_lib.block_log + #biome_lib.block_recheck_list == 0 then
return return
end end
print("[biome_lib] Stand by, playing out the rest of the aircheck mapblock log") print("[biome_lib] Stand by, playing out the rest of the mapblock log")
print("(there are "..#biome_lib.blocklist_aircheck.." entries)...") print("(there are "..(#biome_lib.block_log + #biome_lib.block_recheck_list).." entries)...")
while #biome_lib.blocklist_aircheck > 0 do while #biome_lib.block_log > 0 do
biome_lib:generate_block_with_air_checking(true) biome_lib.generate_block(true)
end
end)
minetest.register_on_shutdown(function()
if #biome_lib.blocklist_aircheck == 0 then
return
end end
print("[biome_lib] Stand by, playing out the rest of the no-aircheck mapblock log") if #biome_lib.block_recheck_list > 0 then
print("(there are "..#biome_lib.blocklist_no_aircheck.." entries)...") biome_lib.block_log = table.copy(biome_lib.block_recheck_list)
while #biome_lib.blocklist_no_aircheck > 0 do while #biome_lib.block_log > 0 do
biome_lib:generate_block_no_aircheck(true) biome_lib.generate_block(true)
end
end end
end) end)
@ -763,22 +762,17 @@ function biome_lib:get_nodedef_field(nodename, fieldname)
end end
if DEBUG then if DEBUG then
biome_lib.last_count_air = 0 biome_lib.last_count = 0
biome_lib.last_count_no_air = 0
function biome_lib.show_pending_block_counts() function biome_lib.show_pending_block_count()
if biome_lib.last_count_air ~= #biome_lib.blocklist_aircheck if biome_lib.last_count ~= #biome_lib.block_log then
or biome_lib.last_count_no_air ~= #biome_lib.blocklist_no_aircheck then biome_lib:dbg("Pending block count: "..(#biome_lib.block_log + #biome_lib.block_recheck_list))
biome_lib:dbg(string.format("Pending block counts, air: %-7i no-air: %i", biome_lib.last_count = #biome_lib.block_log
#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_counts) minetest.after(1, biome_lib.show_pending_block_count)
end end
biome_lib.show_pending_block_counts() biome_lib.show_pending_block_count()
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.blocklist_aircheck[#biome_lib.blocklist_aircheck + 1] = { bmin, bmax } biome_lib.block_log[#biome_lib.block_log + 1] = { bmin, bmax, false }
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)