From 2d004b19eaaac21b4a82fa4ba6736e5e7090041a Mon Sep 17 00:00:00 2001 From: Jeija Date: Fri, 10 Jan 2014 22:29:18 +0100 Subject: [PATCH] First draft of some kind of Action Queue (just like the globalstep queue in to_update), but more flexible and also including delay functionality (mesecon_delayer). The queue is also saved to a file, so that when restarting mesecons, delayers resume to the state they had when the game shut down. Needs testing. --- mesecons/actionqueue.lua | 90 ++++++++++++++++++++++ mesecons/init.lua | 66 +++++----------- mesecons/internal.lua | 153 ++++++++++++++------------------------ mesecons/services.lua | 2 +- mesecons/util.lua | 11 +++ mesecons_delayer/init.lua | 14 +--- 6 files changed, 177 insertions(+), 159 deletions(-) create mode 100644 mesecons/actionqueue.lua diff --git a/mesecons/actionqueue.lua b/mesecons/actionqueue.lua new file mode 100644 index 0000000..8eae192 --- /dev/null +++ b/mesecons/actionqueue.lua @@ -0,0 +1,90 @@ +mesecon.queue.actions={} -- contains all ActionQueue actions + +function mesecon.queue:add_function(name, func) + mesecon.queue.funcs[name] = func +end + +-- If add_action with twice the same overwritecheck and same position are called, the first one is overwritten +-- use overwritecheck nil to never overwrite, but just add the event to the queue +function mesecon.queue:add_action(pos, func, params, time, overwritecheck) + -- Create Action Table: + time = time or 0 -- time <= 0 --> execute, time > 0 --> wait time until execution + action = { pos=pos, + func=func, + params=mesecon:tablecopy(params), + time=time, + owcheck=overwritecheck} + + --print(dump(action)) + -- if not using the queue, (MESECONS_GLOBALSTEP off), just execute the function an we're done + if not MESECONS_GLOBALSTEP then + mesecon.queue:execute(action) + return + end + + -- Otherwise, add the action to the queue + if overwritecheck then -- check if old action has to be overwritten / removed: + for i, ac in ipairs(mesecon.queue.actions) do + if(mesecon:cmpPos(pos, action.pos) + and mesecon:cmpAny(overwritecheck, ac.owcheck)) then + table.remove(mesecon.queue.actions, i) + break + end + end + end + + table.insert(mesecon.queue.actions, action) +end + +-- execute the stored functions on a globalstep +-- if however, the pos of a function is not loaded (get_node_or_nil == nil), do NOT execute the function +-- this makes sure that resuming mesecons circuits when restarting minetest works fine +-- However, even that does not work in some cases, that's why we delay the time the globalsteps +-- start to be execute by 5 seconds +local m_time = 0 +minetest.register_globalstep(function (dtime) + m_time = m_time + dtime + if (m_time < 5) then return end -- don't even try if server has not been running for 2 seconds + local actions = mesecon:tablecopy(mesecon.queue.actions) + mesecon.queue.actions = {} + + for i, action in ipairs(actions) do + if action.time > 0 then + action.time = action.time - dtime + table.insert(mesecon.queue.actions, action) -- will be handled another time + else -- execute and remove + mesecon.queue:execute(action) + end + end +end) + +function mesecon.queue:execute(action) + mesecon.queue.funcs[action.func](action.pos, unpack(action.params)) +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") + +minetest.register_on_shutdown(function() + mesecon.queue.actions = table2file(wpath.."/mesecon_actionqueue", mesecon.queue.actions) +end) diff --git a/mesecons/init.lua b/mesecons/init.lua index 640af4d..8facf73 100644 --- a/mesecons/init.lua +++ b/mesecons/init.lua @@ -42,37 +42,8 @@ -- PUBLIC VARIABLES mesecon={} -- contains all functions and all global variables -mesecon.actions_on={} -- Saves registered function callbacks for mesecon on | DEPRECATED -mesecon.actions_off={} -- Saves registered function callbacks for mesecon off | DEPRECATED -mesecon.actions_change={} -- Saves registered function callbacks for mesecon change | DEPRECATED -mesecon.receptors={} -- saves all information about receptors | DEPRECATED -mesecon.effectors={} -- saves all information about effectors | DEPRECATED -mesecon.conductors={} -- saves all information about conductors | DEPRECATED - - -local wpath = minetest.get_worldpath() -local function read_file(fn) - local f = io.open(fn, "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 write_file(fn, tbl) - local f = io.open(fn, "w") - f:write(minetest.serialize(tbl)) - f:close() -end - -mesecon.to_update = read_file(wpath.."/mesecon_to_update") -mesecon.r_to_update = read_file(wpath.."/mesecon_r_to_update") - -minetest.register_on_shutdown(function() - write_file(wpath.."/mesecon_to_update",mesecon.to_update) - write_file(wpath.."/mesecon_r_to_update",mesecon.r_to_update) -end) +mesecon.queue={} -- contains the ActionQueue +mesecon.queue.funcs={} -- contains all ActionQueue functions -- Settings dofile(minetest.get_modpath("mesecons").."/settings.lua") @@ -86,6 +57,10 @@ dofile(minetest.get_modpath("mesecons").."/presets.lua"); -- mostly things that make the source look cleaner dofile(minetest.get_modpath("mesecons").."/util.lua"); +-- The ActionQueue +-- Saves all the actions that have to be execute in the future +dofile(minetest.get_modpath("mesecons").."/actionqueue.lua"); + -- Internal stuff -- This is the most important file -- it handles signal transmission and basically everything else @@ -101,31 +76,33 @@ dofile(minetest.get_modpath("mesecons").."/legacy.lua"); -- API -- these are the only functions you need to remember -function mesecon:receptor_on_i(pos, rules) +mesecon.queue:add_function("receptor_on", function (pos, rules) rules = rules or mesecon.rules.default for _, rule in ipairs(mesecon:flattenrules(rules)) do local np = mesecon:addPosRule(pos, rule) + -- if area is not loaded, keep trying + if minetest.env:get_node_or_nil(np) == nil then + mesecon.queue:add_action(pos, "receptor_on", {rules}) + end local rulenames = mesecon:rules_link_rule_all(pos, rule) for _, rulename in ipairs(rulenames) do mesecon:turnon(np, rulename) end end -end +end) function mesecon:receptor_on(pos, rules) - if MESECONS_GLOBALSTEP then - rules = rules or mesecon.rules.default - mesecon.r_to_update[#mesecon.r_to_update+1]={pos=pos, rules=rules, action="on"} - else - mesecon:receptor_on_i(pos, rules) - end + mesecon.queue:add_action(pos, "receptor_on", {rules}) end -function mesecon:receptor_off_i(pos, rules) +mesecon.queue:add_function("receptor_off", function (pos, rules) rules = rules or mesecon.rules.default for _, rule in ipairs(mesecon:flattenrules(rules)) do local np = mesecon:addPosRule(pos, rule) + if minetest.env:get_node_or_nil(np) == nil then + mesecon.queue:add_action(pos, "receptor_off", {rules}) + end local rulenames = mesecon:rules_link_rule_all(pos, rule) for _, rulename in ipairs(rulenames) do if not mesecon:connected_to_receptor(np, mesecon:invertRule(rule)) then @@ -135,15 +112,10 @@ function mesecon:receptor_off_i(pos, rules) end end end -end +end) function mesecon:receptor_off(pos, rules) - if MESECONS_GLOBALSTEP then - rules = rules or mesecon.rules.default - mesecon.r_to_update[#mesecon.r_to_update+1]={pos=pos, rules=rules, action="off"} - else - mesecon:receptor_off_i(pos, rules) - end + mesecon.queue:add_action(pos, "receptor_off", {rules}) end diff --git a/mesecons/internal.lua b/mesecons/internal.lua index cb77f5d..f575c83 100644 --- a/mesecons/internal.lua +++ b/mesecons/internal.lua @@ -177,121 +177,76 @@ function mesecon:effector_get_rules(node) return mesecon.rules.default end ---Signals +-- ####################### +-- # Signals (effectors) # +-- ####################### + +-- Activation: +mesecon.queue:add_function("activate", function (pos, rulename) + node = minetest.get_node(pos) + effector = mesecon:get_effector(node.name) + + if effector and effector.action_on then + effector.action_on(pos, node, rulename) + end +end) function mesecon:activate(pos, node, rulename) - if MESECONS_GLOBALSTEP then - if rulename == nil then - for _,rule in ipairs(mesecon:effector_get_rules(node)) do - mesecon:activate(pos, node, rule) - end - return - end - add_action(pos, "on", rulename) - else - local effector = mesecon:get_effector(node.name) - if effector and effector.action_on then - effector.action_on (pos, node, rulename) + if rulename == nil then + for _,rule in ipairs(mesecon:effector_get_rules(node)) do + mesecon:activate(pos, node, rule) end + return end + mesecon.queue:add_action(pos, "activate", {rulename}, nil, rulename) end + +-- Deactivation +mesecon.queue:add_function("deactivate", function (pos, rulename) + node = minetest.get_node(pos) + effector = mesecon:get_effector(node.name) + + if effector and effector.action_off then + effector.action_off(pos, node, rulename) + end +end) + function mesecon:deactivate(pos, node, rulename) - if MESECONS_GLOBALSTEP then - if rulename == nil then - for _,rule in ipairs(mesecon:effector_get_rules(node)) do - mesecon:deactivate(pos, node, rule) - end - return - end - add_action(pos, "off", rulename) - else - local effector = mesecon:get_effector(node.name) - if effector and effector.action_off then - effector.action_off (pos, node, rulename) + if rulename == nil then + for _,rule in ipairs(mesecon:effector_get_rules(node)) do + mesecon:deactivate(pos, node, rule) end + return end + mesecon.queue:add_action(pos, "deactivate", {rulename}, nil, rulename) end + +-- Change +mesecon.queue:add_function("change", function (pos, rulename, changetype) + node = minetest.get_node(pos) + effector = mesecon:get_effector(node.name) + + if effector and effector.action_change then + effector.action_change(pos, node, rulename, changetype) + end +end) + function mesecon:changesignal(pos, node, rulename, newstate) - - newstate = newstate or "on" - --rulename = rulename or mesecon.rules.default - if MESECONS_GLOBALSTEP then - if rulename == nil then - for _,rule in ipairs(mesecon:effector_get_rules(node)) do - mesecon:changesignal(pos, node, rule, newstate) - end + if rulename == nil then + for _,rule in ipairs(mesecon:effector_get_rules(node)) do + mesecon:changesignal(pos, node, rule, newstate) + end return - end - add_action(pos, "c"..newstate, rulename) - else - local effector = mesecon:get_effector(node.name) - if effector and effector.action_change then - effector.action_change (pos, node, rulename, newstate) - end end + + mesecon.queue:add_action(pos, "change", {rulename, newstate}, nil, rulename) end -function execute_actions(dtime) - local nactions = mesecon.to_update - mesecon.to_update = {} - for _,i in ipairs(nactions) do - node = minetest.get_node(i.pos) - if node.name=="ignore" then - add_action(i.pos, i.action, i.rname) - else - effector = mesecon:get_effector(node.name) - if i.action == "on" then - if effector and effector.action_on then - effector.action_on(i.pos, node, i.rname) - end - elseif i.action == "off" then - if effector and effector.action_off then - effector.action_off(i.pos, node, i.rname) - end - elseif i.action == "con" then - if effector and effector.action_change then - effector.action_change(i.pos, node, i.rname, "on") - end - elseif i.action == "coff" then - if effector and effector.action_change then - effector.action_change(i.pos, node, i.rname, "off") - end - end - end - end - local nactions = mesecon.r_to_update - mesecon.r_to_update = {} - for _,i in ipairs(nactions) do - if i.action == "on" then - mesecon:receptor_on_i(i.pos, i.rules) - else - mesecon:receptor_off_i(i.pos,i.rules) - end - end -end - -minetest.register_globalstep(execute_actions) - -function add_action(pos, action, rname) - for _,i in ipairs(mesecon.to_update) do - if i.pos.x == pos.x and i.pos.y == pos.y and i.pos.z == pos.z and i.rname.x == rname.x and i.rname.y == rname.y and i.rname.z == rname.z then - if (i.action == "on" and action == "on") or (i.action == "off" and action == "off") then - --nothing - elseif i.action == "coff" and action == "on" then i.action = "on" - elseif i.action == "con" and action == "off" then i.action = "off" - else - if action == "on" or action == "con" then i.action = "con" end - if action == "off" or action == "coff" then i.action = "coff" end - end - break - end - end - mesecon.to_update[#mesecon.to_update+1] = {pos = pos, action = action, rname = rname} -end - ---Rules +-- ######### +-- # Rules # "Database" for rulenames +-- ######### function mesecon:add_rules(name, rules) mesecon.rules[name] = rules diff --git a/mesecons/services.lua b/mesecons/services.lua index 9d192ae..29b5183 100644 --- a/mesecons/services.lua +++ b/mesecons/services.lua @@ -18,7 +18,7 @@ end mesecon.on_dignode = function (pos, node) if mesecon:is_conductor_on(node) then - mesecon:receptor_off_i(pos, mesecon:conductor_get_rules(node)) + mesecon:receptor_off(pos, mesecon:conductor_get_rules(node)) elseif mesecon:is_receptor_on(node.name) then mesecon:receptor_off(pos, mesecon:receptor_get_rules(node)) end diff --git a/mesecons/util.lua b/mesecons/util.lua index b3ca7a0..ad2a4ae 100644 --- a/mesecons/util.lua +++ b/mesecons/util.lua @@ -181,3 +181,14 @@ function mesecon:tablecopy(table) -- deep table copy return newtable end + +function mesecon:cmpAny(t1, t2) + if type(t1) ~= type(t2) then return false end + if type(t1) ~= "table" and type(t2) ~= "table" then return t1 == t2 end + + for i, e in pairs(t1) do + if not mesecon:cmpAny(e, t2[i]) then return false end + end + + return true +end diff --git a/mesecons_delayer/init.lua b/mesecons_delayer/init.lua index a03737c..4ec0ebc 100644 --- a/mesecons_delayer/init.lua +++ b/mesecons_delayer/init.lua @@ -17,28 +17,18 @@ end -- Functions that are called after the delay time -local delayer_turnon = function(params) - local rules = delayer_get_output_rules(params.node) - mesecon:receptor_on(params.pos, rules) -end - -local delayer_turnoff = function(params) - local rules = delayer_get_output_rules(params.node) - mesecon:receptor_off(params.pos, rules) -end - local delayer_activate = function(pos, node) local def = minetest.registered_nodes[node.name] local time = def.delayer_time minetest.swap_node(pos, {name = def.delayer_onstate, param2=node.param2}) - minetest.after(time, delayer_turnon , {pos = pos, node = node}) + mesecon.queue:add_action(pos, "receptor_on", {rules=delayer_get_output_rules(node)}, time, nil) end local delayer_deactivate = function(pos, node) local def = minetest.registered_nodes[node.name] local time = def.delayer_time minetest.swap_node(pos, {name = def.delayer_offstate, param2=node.param2}) - minetest.after(time, delayer_turnoff, {pos = pos, node = node}) + mesecon.queue:add_action(pos, "receptor_off", {rules=delayer_get_output_rules(node)}, time, nil) end -- Register the 2 (states) x 4 (delay times) delayers