forked from minetest-mods/mesecons
Refactor actionqueue.lua (#501)
Reduce actionqueue complexity, thus faster execution Improve code style Add documentation/explanations
This commit is contained in:
parent
9b58f8db29
commit
68c1729990
@ -1,96 +1,140 @@
|
|||||||
mesecon.queue.actions={} -- contains all ActionQueue actions
|
--[[
|
||||||
|
Mesecons uses something it calls an ActionQueue.
|
||||||
|
|
||||||
function mesecon.queue:add_function(name, func)
|
The ActionQueue holds functions and actions.
|
||||||
mesecon.queue.funcs[name] = func
|
Functions are added on load time with a specified name.
|
||||||
|
Actions are preserved over server restarts.
|
||||||
|
|
||||||
|
Each action consists of a position, the name of an added function to be called,
|
||||||
|
the params that should be used in this function call (additionally to the pos),
|
||||||
|
the time after which it should be executed, an optional overwritecheck and a
|
||||||
|
priority.
|
||||||
|
|
||||||
|
If time = 0, the action will be executed in the next globalstep, otherwise the
|
||||||
|
earliest globalstep when it will be executed is the after next globalstep.
|
||||||
|
|
||||||
|
It is guaranteed, that for two actions ac1, ac2 where ac1 ~= ac2,
|
||||||
|
ac1.time == ac2.time, ac1.priority == ac2.priority and ac1 was added earlier
|
||||||
|
than ac2, ac1 will be executed before ac2 (but in the same globalstep).
|
||||||
|
|
||||||
|
Note: Do not pass references in params, as they can not be preserved.
|
||||||
|
|
||||||
|
Also note: Some of the guarantees here might be dropped at some time.
|
||||||
|
]]
|
||||||
|
|
||||||
|
|
||||||
|
-- localize for speed
|
||||||
|
local queue = mesecon.queue
|
||||||
|
|
||||||
|
queue.actions = {} -- contains all ActionQueue actions
|
||||||
|
|
||||||
|
function queue:add_function(name, func)
|
||||||
|
queue.funcs[name] = func
|
||||||
end
|
end
|
||||||
|
|
||||||
-- If add_action with twice the same overwritecheck and same position are called, the first one is overwritten
|
-- 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
|
-- 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
|
-- priority specifies the order actions are executed within one globalstep, highest first
|
||||||
-- should be between 0 and 1
|
-- 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:
|
-- Create Action Table:
|
||||||
time = time or 0 -- time <= 0 --> execute, time > 0 --> wait time until execution
|
time = time or 0 -- time <= 0 --> execute, time > 0 --> wait time until execution
|
||||||
priority = priority or 1
|
priority = priority or 1
|
||||||
local action = { pos=mesecon.tablecopy(pos),
|
local action = {
|
||||||
|
pos = mesecon.tablecopy(pos),
|
||||||
func = func,
|
func = func,
|
||||||
params = mesecon.tablecopy(params or {}),
|
params = mesecon.tablecopy(params or {}),
|
||||||
time = time,
|
time = time,
|
||||||
owcheck = (overwritecheck and mesecon.tablecopy(overwritecheck)) or nil,
|
owcheck = (overwritecheck and mesecon.tablecopy(overwritecheck)) or nil,
|
||||||
priority=priority}
|
priority = priority
|
||||||
|
}
|
||||||
|
|
||||||
local toremove = nil
|
-- check if old action has to be overwritten / removed:
|
||||||
-- Otherwise, add the action to the queue
|
if overwritecheck then
|
||||||
if overwritecheck then -- check if old action has to be overwritten / removed:
|
for i, ac in ipairs(queue.actions) do
|
||||||
for i, ac in ipairs(mesecon.queue.actions) do
|
if vector.equals(pos, ac.pos)
|
||||||
if(vector.equals(pos, ac.pos)
|
and mesecon.cmpAny(overwritecheck, ac.owcheck) then
|
||||||
and mesecon.cmpAny(overwritecheck, ac.owcheck)) then
|
-- remove the old action
|
||||||
toremove = i
|
table.remove(queue.actions, i)
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if (toremove ~= nil) then
|
table.insert(queue.actions, action)
|
||||||
table.remove(mesecon.queue.actions, toremove)
|
|
||||||
end
|
|
||||||
|
|
||||||
table.insert(mesecon.queue.actions, action)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- execute the stored functions on a globalstep
|
-- 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
|
-- 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
|
-- this makes sure that resuming mesecons circuits when restarting minetest works fine (hm, where do we do this?)
|
||||||
-- However, even that does not work in some cases, that's why we delay the time the globalsteps
|
-- 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
|
-- start to be execute by 4 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
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return highesti
|
local function globalstep_func(dtime)
|
||||||
end
|
local actions = queue.actions
|
||||||
|
-- split into two categories:
|
||||||
local m_time = 0
|
-- actions_now: actions to execute now
|
||||||
local resumetime = mesecon.setting("resumetime", 4)
|
-- queue.actions: actions to execute later
|
||||||
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 = {}
|
local actions_now = {}
|
||||||
|
queue.actions = {}
|
||||||
|
|
||||||
mesecon.queue.actions = {}
|
for _, ac in ipairs(actions) do
|
||||||
|
|
||||||
-- 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
|
if ac.time > 0 then
|
||||||
ac.time = ac.time - dtime -- executed later
|
-- action ac is to be executed later
|
||||||
table.insert(mesecon.queue.actions, ac)
|
-- ~> insert into queue.actions
|
||||||
|
ac.time = ac.time - dtime
|
||||||
|
table.insert(queue.actions, ac)
|
||||||
else
|
else
|
||||||
|
-- action ac is to be executed now
|
||||||
|
-- ~> insert into actions_now
|
||||||
table.insert(actions_now, ac)
|
table.insert(actions_now, ac)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
while(#actions_now > 0) do -- execute highest priorities first, until all are executed
|
-- stable-sort the executed actions after their priority
|
||||||
local hp = get_highest_priority(actions_now)
|
-- some constructions might depend on the execution order, hence we first
|
||||||
mesecon.queue:execute(actions_now[hp])
|
-- execute the actions that had a lower index in actions_now
|
||||||
table.remove(actions_now, hp)
|
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
|
||||||
end)
|
end)
|
||||||
|
|
||||||
function mesecon.queue:execute(action)
|
-- execute highest priorities first, until all are executed
|
||||||
|
for _, ac in ipairs(actions_now) do
|
||||||
|
queue:execute(ac)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- delay the time the globalsteps start to be execute by 4 seconds
|
||||||
|
do
|
||||||
|
local m_time = 0
|
||||||
|
local resumetime = mesecon.setting("resumetime", 4)
|
||||||
|
local globalstep_func_index = #minetest.registered_globalsteps + 1
|
||||||
|
|
||||||
|
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
|
||||||
|
-- replace this globalstep function
|
||||||
|
minetest.registered_globalsteps[globalstep_func_index] = globalstep_func
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
function queue:execute(action)
|
||||||
-- ignore if action queue function name doesn't exist,
|
-- ignore if action queue function name doesn't exist,
|
||||||
-- (e.g. in case the action queue savegame was written by an old mesecons version)
|
-- (e.g. in case the action queue savegame was written by an old mesecons version)
|
||||||
if mesecon.queue.funcs[action.func] then
|
if queue.funcs[action.func] then
|
||||||
mesecon.queue.funcs[action.func](action.pos, unpack(action.params))
|
queue.funcs[action.func](action.pos, unpack(action.params))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -98,8 +142,8 @@ end
|
|||||||
-- Store and read the ActionQueue to / from a file
|
-- Store and read the ActionQueue to / from a file
|
||||||
-- so that upcoming actions are remembered when the game
|
-- so that upcoming actions are remembered when the game
|
||||||
-- is restarted
|
-- is restarted
|
||||||
mesecon.queue.actions = mesecon.file2table("mesecon_actionqueue")
|
queue.actions = mesecon.file2table("mesecon_actionqueue")
|
||||||
|
|
||||||
minetest.register_on_shutdown(function()
|
minetest.register_on_shutdown(function()
|
||||||
mesecon.table2file("mesecon_actionqueue", mesecon.queue.actions)
|
mesecon.table2file("mesecon_actionqueue", queue.actions)
|
||||||
end)
|
end)
|
||||||
|
Loading…
Reference in New Issue
Block a user