From cfd4f7c287543f7f14d05e90c106fc14a4be8666 Mon Sep 17 00:00:00 2001 From: Jeija Date: Fri, 19 Feb 2016 12:11:38 +0100 Subject: [PATCH] Force-load areas with mesecon usage This is a major speedup for large mesecon machines / structures. Force-loaded areas are stored in a file to be persistent over server reboots. By default, areas are unloaded after 10 minutes (600s) without usage, this can be customized with the mesecon.forceload_timeout setting. Please turn max_forceloaded_blocks up for better performance. --- mesecons/actionqueue.lua | 21 ++---------- mesecons/internal.lua | 62 +++++++++++++++++++++++----------- mesecons/util.lua | 72 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 116 insertions(+), 39 deletions(-) diff --git a/mesecons/actionqueue.lua b/mesecons/actionqueue.lua index a773008..e300ea7 100644 --- a/mesecons/actionqueue.lua +++ b/mesecons/actionqueue.lua @@ -94,25 +94,8 @@ end -- Store and read the ActionQueue to / from a file -- so that upcoming actions are remembered when the game -- is restarted - -local wpath = minetest.get_worldpath() -local function file2table(filename) - local f = io.open(filename, "r") - if f==nil then return {} end - local t = f:read("*all") - f:close() - if t=="" or t==nil then return {} end - return minetest.deserialize(t) -end - -local function table2file(filename, table) - local f = io.open(filename, "w") - f:write(minetest.serialize(table)) - f:close() -end - -mesecon.queue.actions = file2table(wpath.."/mesecon_actionqueue") +mesecon.queue.actions = mesecon.file2table("mesecon_actionqueue") minetest.register_on_shutdown(function() - mesecon.queue.actions = table2file(wpath.."/mesecon_actionqueue", mesecon.queue.actions) + mesecon.table2file("mesecon_actionqueue", mesecon.queue.actions) end) diff --git a/mesecons/internal.lua b/mesecons/internal.lua index 41c5c52..fc1cf8d 100644 --- a/mesecons/internal.lua +++ b/mesecons/internal.lua @@ -77,6 +77,8 @@ function mesecon.get_conductor(nodename) end function mesecon.get_any_outputrules (node) + if not node then return nil end + if mesecon.is_conductor(node.name) then return mesecon.conductor_get_rules(node) elseif mesecon.is_receptor(node.name) then @@ -85,6 +87,8 @@ function mesecon.get_any_outputrules (node) end function mesecon.get_any_inputrules (node) + if not node then return nil end + if mesecon.is_conductor(node.name) then return mesecon.conductor_get_rules(node) elseif mesecon.is_effector(node.name) then @@ -182,7 +186,9 @@ end -- Activation: mesecon.queue:add_function("activate", function (pos, rulename) - local node = minetest.get_node(pos) + local node = mesecon.get_node_force(pos) + if not node then return end + local effector = mesecon.get_effector(node.name) if effector and effector.action_on then @@ -203,7 +209,9 @@ end -- Deactivation mesecon.queue:add_function("deactivate", function (pos, rulename) - local node = minetest.get_node(pos) + local node = mesecon.get_node_force(pos) + if not node then return end + local effector = mesecon.get_effector(node.name) if effector and effector.action_off then @@ -224,7 +232,9 @@ end -- Change mesecon.queue:add_function("change", function (pos, rulename, changetype) - local node = minetest.get_node(pos) + local node = mesecon.get_node_force(pos) + if not node then return end + local effector = mesecon.get_effector(node.name) if effector and effector.action_change then @@ -249,6 +259,8 @@ end -- Conductors function mesecon.is_conductor_on(node, rulename) + if not node then return false end + local conductor = mesecon.get_conductor(node.name) if conductor then if conductor.state then @@ -263,10 +275,13 @@ function mesecon.is_conductor_on(node, rulename) return mesecon.get_bit(binstate, bit) end end + return false end function mesecon.is_conductor_off(node, rulename) + if not node then return false end + local conductor = mesecon.get_conductor(node.name) if conductor then if conductor.state then @@ -281,6 +296,7 @@ function mesecon.is_conductor_off(node, rulename) return not mesecon.get_bit(binstate, bit) end end + return false end @@ -340,7 +356,7 @@ end -- some more general high-level stuff function mesecon.is_power_on(pos, rulename) - local node = minetest.get_node(pos) + local node = mesecon.get_node_force(pos) if mesecon.is_conductor_on(node, rulename) or mesecon.is_receptor_on(node.name) then return true end @@ -348,7 +364,7 @@ function mesecon.is_power_on(pos, rulename) end function mesecon.is_power_off(pos, rulename) - local node = minetest.get_node(pos) + local node = mesecon.get_node_force(pos) if mesecon.is_conductor_off(node, rulename) or mesecon.is_receptor_off(node.name) then return true end @@ -361,10 +377,11 @@ function mesecon.turnon(pos, link) local depth = 1 while frontiers[depth] do local f = frontiers[depth] - local node = minetest.get_node_or_nil(f.pos) + local node = mesecon.get_node_force(f.pos) -- area not loaded, postpone action if not node then + print("Mesecons: Postponing action!") mesecon.queue:add_action(f.pos, "turnon", {link}, nil, true) elseif mesecon.is_conductor_off(node, f.link) then local rules = mesecon.conductor_get_rules(node) @@ -377,7 +394,7 @@ function mesecon.turnon(pos, link) local np = vector.add(f.pos, r) -- area not loaded, postpone action - if not minetest.get_node_or_nil(np) then + if not mesecon.get_node_force(np) then mesecon.queue:add_action(np, "turnon", {rulename}, nil, true) else @@ -407,7 +424,7 @@ function mesecon.turnoff(pos, link) local depth = 1 while frontiers[depth] do local f = frontiers[depth] - local node = minetest.get_node_or_nil(f.pos) + local node = mesecon.get_node_force(f.pos) -- area not loaded, postpone action if not node then @@ -423,7 +440,7 @@ function mesecon.turnoff(pos, link) local np = vector.add(f.pos, r) -- area not loaded, postpone action - if not minetest.get_node_or_nil(np) then + if not mesecon.get_node_force(np) then mesecon.queue:add_action(np, "turnoff", {rulename}, nil, true) else @@ -449,7 +466,8 @@ end) function mesecon.connected_to_receptor(pos, link) - local node = minetest.get_node(pos) + local node = mesecon.get_node_force(pos) + if not node then return false end -- Check if conductors around are connected local rules = mesecon.get_any_inputrules(node) @@ -476,7 +494,7 @@ function mesecon.find_receptor_on(pos, link) local depth = 1 while frontiers[depth] do local f = frontiers[depth] - local node = minetest.get_node_or_nil(f.pos) + local node = mesecon.get_node_force(f.pos) if not node then return false end if mesecon.is_receptor_on(node.name) then return true end @@ -503,8 +521,9 @@ function mesecon.find_receptor_on(pos, link) end function mesecon.rules_link(output, input, dug_outputrules) --output/input are positions (outputrules optional, used if node has been dug), second return value: the name of the affected input rule - local outputnode = minetest.get_node(output) - local inputnode = minetest.get_node(input) + local outputnode = mesecon.get_node_force(output) + local inputnode = mesecon.get_node_force(input) + local outputrules = dug_outputrules or mesecon.get_any_outputrules (outputnode) local inputrules = mesecon.get_any_inputrules (inputnode) if not outputrules or not inputrules then @@ -522,12 +541,13 @@ function mesecon.rules_link(output, input, dug_outputrules) --output/input are p end end end + return false end function mesecon.rules_link_rule_all(output, rule) local input = vector.add(output, rule) - local inputnode = minetest.get_node(input) + local inputnode = mesecon.get_node_force(input) local inputrules = mesecon.get_any_inputrules (inputnode) if not inputrules then return {} @@ -540,13 +560,14 @@ function mesecon.rules_link_rule_all(output, rule) table.insert(rules, inputrule) end end + return rules end function mesecon.rules_link_rule_all_inverted(input, rule) --local irule = mesecon.invertRule(rule) local output = vector.add(input, rule) - local outputnode = minetest.get_node(output) + local outputnode = mesecon.get_node_force(output) local outputrules = mesecon.get_any_outputrules (outputnode) if not outputrules then return {} @@ -566,7 +587,7 @@ function mesecon.rules_link_anydir(pos1, pos2) end function mesecon.is_powered(pos, rule) - local node = minetest.get_node(pos) + local node = mesecon.get_node_force(pos) local rules = mesecon.get_any_inputrules(node) if not rules then return false end @@ -578,9 +599,10 @@ function mesecon.is_powered(pos, rule) local rulenames = mesecon.rules_link_rule_all_inverted(pos, rule) for _, rname in ipairs(rulenames) do local np = vector.add(pos, rname) - local nn = minetest.get_node(np) - if (mesecon.is_conductor_on (nn, mesecon.invertRule(rname)) - or mesecon.is_receptor_on (nn.name)) then + local nn = mesecon.get_node_force(np) + + if (mesecon.is_conductor_on(nn, mesecon.invertRule(rname)) + or mesecon.is_receptor_on(nn.name)) then table.insert(sourcepos, np) end end @@ -589,7 +611,7 @@ function mesecon.is_powered(pos, rule) local rulenames = mesecon.rules_link_rule_all_inverted(pos, rule) for _, rname in ipairs(rulenames) do local np = vector.add(pos, rname) - local nn = minetest.get_node(np) + local nn = mesecon.get_node_force(np) if (mesecon.is_conductor_on (nn, mesecon.invertRule(rname)) or mesecon.is_receptor_on (nn.name)) then table.insert(sourcepos, np) diff --git a/mesecons/util.lua b/mesecons/util.lua index e7b6866..3827dce 100644 --- a/mesecons/util.lua +++ b/mesecons/util.lua @@ -201,3 +201,75 @@ function mesecon.flipstate(pos, node) return newstate end + +-- File writing / reading utilities +local wpath = minetest.get_worldpath() +function mesecon.file2table(filename) + local f = io.open(wpath..DIR_DELIM..filename, "r") + if f == nil then return {} end + local t = f:read("*all") + f:close() + if t == "" or t == nil then return {} end + return minetest.deserialize(t) +end + +function mesecon.table2file(filename, table) + local f = io.open(wpath..DIR_DELIM..filename, "w") + f:write(minetest.serialize(table)) + f:close() +end + +-- Forceloading: Force server to load area if node is nil +local BLOCKSIZE = 16 + +-- convert node position --> block hash +local function hash_blockpos(pos) + return minetest.hash_node_position({ + x = math.floor(pos.x/BLOCKSIZE), + y = math.floor(pos.y/BLOCKSIZE), + z = math.floor(pos.z/BLOCKSIZE) + }) +end + +-- convert block hash --> node position +local function unhash_blockpos(hash) + return vector.multiply(minetest.get_position_from_hash(hash), BLOCKSIZE) +end + +mesecon.forceloaded_blocks = {} + +-- get node and force-load area +function mesecon.get_node_force(pos) + local hash = hash_blockpos(pos) + + if mesecon.forceloaded_blocks[hash] == nil then + -- if no more forceload spaces are available, try again next time + if minetest.forceload_block(pos) then + mesecon.forceloaded_blocks[hash] = 0 + end + else + mesecon.forceloaded_blocks[hash] = 0 + end + + return minetest.get_node_or_nil(pos) +end + +minetest.register_globalstep(function (dtime) + for hash, time in pairs(mesecon.forceloaded_blocks) do + -- unload forceloaded blocks after 10 minutes without usage + if (time > mesecon.setting("forceload_timeout", 600)) then + minetest.forceload_free_block(unhash_blockpos(hash)) + mesecon.forceloaded_blocks[hash] = nil + else + mesecon.forceloaded_blocks[hash] = time + dtime + end + end +end) + +-- Store and read the forceloaded blocks to / from a file +-- so that those blocks are remembered when the game +-- is restarted +mesecon.forceloaded_blocks = mesecon.file2table("mesecon_forceloaded") +minetest.register_on_shutdown(function() + mesecon.table2file("mesecon_forceloaded", mesecon.forceloaded_blocks) +end)