From b00fab6c48e210bb4a960acf1317b08735c62384 Mon Sep 17 00:00:00 2001 From: DS-Minetest Date: Sat, 28 Dec 2019 17:36:48 +0100 Subject: [PATCH] refactor actionqueue.lua --- mesecons/actionqueue.lua | 150 ++++++++++++++++++++++----------------- 1 file changed, 86 insertions(+), 64 deletions(-) diff --git a/mesecons/actionqueue.lua b/mesecons/actionqueue.lua index f3479ce..6790a29 100644 --- a/mesecons/actionqueue.lua +++ b/mesecons/actionqueue.lua @@ -1,96 +1,118 @@ -mesecon.queue.actions={} -- contains all ActionQueue actions +-- localize for speed +local queue = mesecon.queue -function mesecon.queue:add_function(name, func) - mesecon.queue.funcs[name] = func +queue.actions = {} -- contains all ActionQueue actions + +function queue:add_function(name, func) + 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 -- priority specifies the order actions are executed within one globalstep, highest first -- should be between 0 and 1 -function mesecon.queue:add_action(pos, func, params, time, overwritecheck, priority) +function queue:add_action(pos, func, params, time, overwritecheck, priority) -- Create Action Table: time = time or 0 -- time <= 0 --> execute, time > 0 --> wait time until execution priority = priority or 1 - local action = { pos=mesecon.tablecopy(pos), - func=func, - params=mesecon.tablecopy(params or {}), - time=time, - owcheck=(overwritecheck and mesecon.tablecopy(overwritecheck)) or nil, - priority=priority} + local action = { + pos = mesecon.tablecopy(pos), + func = func, + params = mesecon.tablecopy(params or {}), + time = time, + owcheck = (overwritecheck and mesecon.tablecopy(overwritecheck)) or nil, + priority = priority + } - local toremove = nil - -- 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(vector.equals(pos, ac.pos) - and mesecon.cmpAny(overwritecheck, ac.owcheck)) then - toremove = i - break + -- check if old action has to be overwritten / removed: + if overwritecheck then + for i, ac in ipairs(queue.actions) do + if vector.equals(pos, ac.pos) + and mesecon.cmpAny(overwritecheck, ac.owcheck) then + -- replace the old action + queue.actions[i] = action + return end end end - if (toremove ~= nil) then - table.remove(mesecon.queue.actions, toremove) - end - - table.insert(mesecon.queue.actions, action) + -- otherwise just add to queue + table.insert(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 get_highest_priority = function (actions) - local highestp = -1 - local highesti - for i, ac in ipairs(actions) do - if ac.priority > highestp then - highestp = ac.priority - highesti = i +-- start to be execute by 5 (4?) seconds + +local function globalstep_func(dtime) + -- sort out the actions to execute now (actions_now) + local actions_now = {} + local actions_count = #queue.actions + + -- iterating downwards makes it easier to remove actions + for i = actions_count, 1, -1 do + local ac = queue.actions[i] + ac.time = ac.time - dtime + + if ac.time <= 0 then + -- action ac is to be executed now + -- ~> insert into actions_now + table.insert(actions_now, ac) + + -- ~> remove from queue.actions + queue.actions[i] = queue.actions[actions_count] + queue.actions[actions_count] = nil + actions_count = actions_count - 1 end end - return highesti + -- stable-sort the executed actions after their priority + -- (some constructions might depend on the execution order for acions with delay 0) + -- note that the actions were added in inverse order because of the downwards iteration, + -- hence we first execute the actions that had a higher index in actions_now + local old_action_order = {} + for i, ac in ipairs(actions_now) do + old_action_order[ac] = i + end + table.sort(actions_now, function(ac1, ac2) + if ac1.priority ~= ac2.priority then + return ac1.priority > ac2.priority + else + return old_action_order[ac1] > old_action_order[ac2] + end + end) + + -- execute highest priorities first, until all are executed + for i, ac in ipairs(actions_now) do + queue:execute(ac) + end end -local m_time = 0 -local resumetime = mesecon.setting("resumetime", 4) -minetest.register_globalstep(function (dtime) - m_time = m_time + dtime - -- don't even try if server has not been running for XY seconds; resumetime = time to wait - -- after starting the server before processing the ActionQueue, don't set this too low - if (m_time < resumetime) then return end - local actions = mesecon.tablecopy(mesecon.queue.actions) - local actions_now={} +-- delay the time the globalsteps start to be execute by 5 (4?) seconds +do + local m_time = 0 + local resumetime = mesecon.setting("resumetime", 4) + local globalstep_func_index = #minetest.registered_globalsteps + 1 - mesecon.queue.actions = {} - - -- sort actions into two categories: - -- those toexecute now (actions_now) and those to execute later (mesecon.queue.actions) - for i, ac in ipairs(actions) do - if ac.time > 0 then - ac.time = ac.time - dtime -- executed later - table.insert(mesecon.queue.actions, ac) - else - table.insert(actions_now, ac) + minetest.register_globalstep(function (dtime) + m_time = m_time + dtime + -- don't even try if server has not been running for XY seconds; resumetime = time to wait + -- after starting the server before processing the ActionQueue, don't set this too low + if m_time < resumetime then + return end - end + -- replace this globalstep function + minetest.registered_globalsteps[globalstep_func_index] = globalstep_func + end) +end - while(#actions_now > 0) do -- execute highest priorities first, until all are executed - local hp = get_highest_priority(actions_now) - mesecon.queue:execute(actions_now[hp]) - table.remove(actions_now, hp) - end -end) - -function mesecon.queue:execute(action) +function queue:execute(action) -- ignore if action queue function name doesn't exist, -- (e.g. in case the action queue savegame was written by an old mesecons version) - if mesecon.queue.funcs[action.func] then - mesecon.queue.funcs[action.func](action.pos, unpack(action.params)) + if queue.funcs[action.func] then + queue.funcs[action.func](action.pos, unpack(action.params)) end end @@ -98,8 +120,8 @@ end -- Store and read the ActionQueue to / from a file -- so that upcoming actions are remembered when the game -- is restarted -mesecon.queue.actions = mesecon.file2table("mesecon_actionqueue") +queue.actions = mesecon.file2table("mesecon_actionqueue") minetest.register_on_shutdown(function() - mesecon.table2file("mesecon_actionqueue", mesecon.queue.actions) + mesecon.table2file("mesecon_actionqueue", queue.actions) end)