local store=minetest.get_mod_storage() do -- DESTROY minetest's forceload limit with FACTS and LOGIC local smt=getmetatable(minetest.settings) local oldget=smt.get local forceload_block=minetest.forceload_block local unlimit function smt:get(key,...) if self==minetest.settings and key=="max_forceloaded_blocks" and unlimit then return "1e1000" end return oldget(self,key,...) end function minetest.forceload_block(...) unlimit=true local ret=forceload_block(...) unlimit=false return ret end end local forceload_block=minetest.forceload_block local forceload_free_block=minetest.forceload_free_block local function blockpos_to_id(bp) return ("%i,%i,%i"):format(bp.x,bp.y,bp.z) end local function id_to_blockpos(i) local x,y,z=i:match("([^,]+),([^,]+),([^,]+)") x,y,z=tonumber(x),tonumber(y),tonumber(z) assert(x and y and z,dump{x,y,z}) return vector.new(x,y,z) end local persistent_floads=minetest.deserialize(store:get_string("forceloads")) or {} local forceloads={ blocks={}, floads={} } local floaded=false minetest.after(0,function() for name,block in pairs(persistent_floads) do forceloads.blocks[block]=forceloads.blocks[block] or {} forceloads.blocks[block][name]=1 forceloads.floads[name]=block forceload_block(vector.multiply(id_to_blockpos(block),16),true) end floaded=true end) local planned=false local function savepf() if planned then return end planned=true minetest.after(0,function() if not planned then return end store:set_string("forceloads",minetest.serialize(persistent_floads)) planned=false end) end local lib={} lib.FORCELOAD_DURING_INIT="can't forceload during init" lib.NO_SUCH_FORCELOAD="no such forceload" local start,stop,query start = function(id,blockpos,persistent) if not floaded then return false,lib.FORCELOAD_DURING_INIT end blockpos=vector.floor(blockpos) assert(type(id)=="string","id must be string") local bpi=blockpos_to_id(blockpos) if forceloads.floads[id] then if forceloads.floads[id]==bpi then if (not persistent)~=(not persistent_floads[id]) then persistent_floads[id]=persistent and bpi or nil forceloads.blocks[bpi][id]=persistent and 1 or 0 savepf() end return true end stop(id) end forceloads.floads[id]=bpi local block=forceloads.blocks[bpi] or {} forceloads.blocks[bpi]=block local wasfree=not next(block) block[id]=persistent and 1 or 0 if persistent then persistent_floads[id]=bpi savepf() end local pos=vector.multiply(blockpos,16) if wasfree then assert(forceload_block(pos,true),"forceload failed! shame on minetest") end return true end stop = function(id) local bpi=forceloads.floads[id] if not bpi then return false,lib.NO_SUCH_FORCELOAD end local block=forceloads.blocks[bpi] or {} assert(block[id],"forceload inconsistency") local persistent=block[id]==1 if persistent then assert(persistent_floads[id],"persistent forceload inconsistency") persistent_floads[id]=nil savepf() end local wasfree=not next(block) block[id]=nil forceloads.floads[id]=nil block=next(block) and block or nil forceloads.blocks[bpi]=block local blockpos=id_to_blockpos(bpi) local pos=vector.multiply(blockpos,16) if not (wasfree or block) then forceload_free_block(pos,true) end return true end local function areaiter(id,op1,op2,fn) for y=op1.y,op2.y do for x=op1.x,op2.x do for z=op1.z,op2.z do local blockpos=vector.new(x,y,z) if not vector.equals(blockpos,op1) and not vector.equals(blockpos,op2) then local name=("m%s:%s"):format(blockpos_to_id(blockpos),id) local ret={fn(blockpos,name)} if ret[1] then return unpack(ret,2) end end end end end end function lib.start(id,p1,D,E) local p2,persistent if type(D)=="table" and D.x then p2,persistent=D,E else p2,persistent=p1,D end p1,p2=vector.floor(p1),vector.floor(p2) p1,p2=vector.combine(p1,p2,math.min),vector.combine(p1,p2,math.max) local oldblocks={} local floads=forceloads.floads local op1,op2=floads["a:"..id],floads["b:"..id] op1=op1 and id_to_blockpos(op1) op2=op2 and id_to_blockpos(op2) if op2 then oldblocks["b:"..id]=true end if op2 and not op1 then error("forceload area inconsistency") end if op1 and op2 then areaiter(id,op1,op2,function(blockpos,name) assert(floads[name],"forceload area inconsistency") oldblocks[name]=true end) end local ok,err=start("a:"..id,p1,persistent) if not ok then return ok,err end if not vector.equals(p1,p2) then assert(start("b:"..id,p2,persistent)) oldblocks["b:"..id]=nil areaiter(id,p1,p2,function(blockpos,name) assert(start(name,blockpos,persistent)) oldblocks[name]=nil end) end for name,_ in pairs(oldblocks) do assert(stop(name),"forceload area inconsistency") end return true end function lib.stop(id) local floads=forceloads.floads local op1,op2=floads["a:"..id],floads["b:"..id] op1=op1 and id_to_blockpos(op1) op2=op2 and id_to_blockpos(op2) local ok,err=stop("a:"..id) if not ok then return ok,err end if op2 then assert(stop("b:"..id),"forceload area inconsistency") areaiter(id,op1,op2,function(blockpos,name) assert(stop(name),"forceload area inconsistency") end) end return true end local function ge(a,b) return a>=b and 0 or 1 end local function le(a,b) return a<=b and 0 or 1 end local function process_flist(fl) local floads={} for k,v in pairs(fl) do local c=k:sub(1,1) if c=="a" or c=="b" or c=="m" then local ct,id=k:match("(.-):(.+)") local mi,ma,pers local fload=floads[id] if not fload then local op1,op2=forceloads.floads["a:"..id],forceloads.floads["b:"..id] op1=op1 and id_to_blockpos(op1) op2=op2 and id_to_blockpos(op2) fload={ pos1=op1, pos2=op2 or op1, persistent=v.persistent } floads[id]=fload end elseif c=="i" then local bpi,tra,n=k:match("i:(.-):(.-):(.+)") local blockpos=id_to_blockpos(bpi) n=tonumber(n) or error("wtf") local id="(minetest)"..tra..minetest.pos_to_string(blockpos) local fload=floads[id] or { pos1 = v.blockpos, pos2 = v.blockpos, persistent = v.persistent, count = -math.huge } floads[id]=fload fload.count=math.max(n+1,fload.count) end end return floads end query = function(obj) if not obj then local floads={} for k,v in pairs(forceloads.floads) do floads[k]=query(k) end return floads elseif type(obj)=="string" then local fload=forceloads.floads[obj] return fload and { blockpos = id_to_blockpos(fload), persistent = forceloads.blocks[fload][obj]==1 } or nil end obj=vector.apply(obj,math.floor) local bpi=blockpos_to_id(obj) local floads={} for k,v in pairs(forceloads.blocks[bpi] or {}) do floads[k]={ blockpos = vector.new(obj), persistent = v==1 } end return floads end function lib.query(obj,o2) if not obj then return process_flist(query()) elseif type(obj)=="string" then local mt,tra,p=obj:match("(%(minetest%))(.)(.+)") if mt then local blockpos=minetest.string_to_pos(p) local floads=process_flist(query(blockpos)) return floads[obj] end local id="a:"..obj local floads=process_flist({[id]=query(id)}) return floads[obj] end if o2 then local floads={} for x=obj.x,o2.x do for y=obj.y,o2.y do for z=obj.z,o2.z do for k,v in pairs(query(vector.new(x,y,z))) do floads[k]=v end end end end return process_flist(floads) end return process_flist(query(obj)) end minetest.forceload_block=nil minetest.forceload_free_block=nil local function mtfbp(pos,transient,n) local blockpos=vector.apply(vector.multiply(pos,1/16),math.floor) local id=("i:%s:%s:%s"):format(blockpos_to_id(blockpos),transient and "T" or "P",n) return id,blockpos end function minetest.forceload_block(pos,transient) local n=0 local id,blockpos=mtfbp(pos,transient,n) while query(id) do n=n+1 id,blockpos=mtfbp(pos,transient,n) end return not not start(id,blockpos,not transient) end function minetest.forceload_free_block(pos,transient) local n=0 local id=mtfbp(pos,transient,n) while query(id) do oldid=id n=n+1 id=mtfbp(pos,transient,n) end if not oldid then return nil end return not not (stop(oldid)) end _G[minetest.get_current_modname()]=lib ---[[ TEST ('---[[' = enabled, '--[[' = disabled) do local function pwrap(fn) return function(...) local args={...} local ret={xpcall(function()return fn(unpack(args))end,debug.traceback)} if not ret[1] then return unpack(ret) end return unpack(ret,2) end end local function chatify(fname,fn) return pwrap(function(name,param) local ret={fn(unpack(minetest.deserialize(("return {%s}"):format(param))))} return true,fname..": "..dump(ret) end) end minetest.register_chatcommand("afl:start",{ params = "{...}", privs = {privs=true}, func = chatify("start",function(id,blockpos,b2,...) if type(b2)=="string" then b2=assert(minetest.string_to_pos(b2)) end return lib.start(id,minetest.string_to_pos(blockpos),b2,...) end) }) minetest.register_chatcommand("afl:stop",{ params = "{...}", privs = {privs=true}, func = chatify("stop",lib.stop) }) minetest.register_chatcommand("afl:forceload_block",{ params = "{...}", privs = {privs=true}, func = chatify("forceload_block",minetest.forceload_block) }) minetest.register_chatcommand("afl:forceload_free_block",{ params = "{...}", privs = {privs=true}, func = chatify("forceload_free_block",minetest.forceload_free_block) }) minetest.register_chatcommand("afl:query",{ params = "{...}", privs = {privs=true}, func = chatify("query",lib.query) }) minetest.register_chatcommand("afl:query_raw",{ params = "{...}", privs = {privs=true}, func = chatify("query_raw",query) }) for k,v in ipairs{"unknown","emerging","loaded","active"} do minetest.register_chatcommand("afl:check_"..v,{ params="{...}", privs = {privs=true}, func = chatify("check_"..v,function(bp) return minetest.compare_block_status(vector.multiply(minetest.string_to_pos(bp),16),v) end) }) end end --]]