mirror of
				https://github.com/minetest-mods/mesecons.git
				synced 2025-11-04 04:55:26 +01:00 
			
		
		
		
	Refactor actionqueue.lua (#501)
Reduce actionqueue complexity, thus faster execution Improve code style Add documentation/explanations
This commit is contained in:
		@@ -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)
 | 
			
		||||
	mesecon.queue.funcs[name] = func
 | 
			
		||||
The ActionQueue holds functions and actions.
 | 
			
		||||
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
 | 
			
		||||
 | 
			
		||||
-- 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
 | 
			
		||||
	 -- 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
 | 
			
		||||
				-- remove the old action
 | 
			
		||||
				table.remove(queue.actions, i)
 | 
			
		||||
				break
 | 
			
		||||
			end
 | 
			
		||||
		end
 | 
			
		||||
	end
 | 
			
		||||
 | 
			
		||||
	if (toremove ~= nil) then
 | 
			
		||||
		table.remove(mesecon.queue.actions, toremove)
 | 
			
		||||
	end
 | 
			
		||||
 | 
			
		||||
	table.insert(mesecon.queue.actions, action)
 | 
			
		||||
	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
 | 
			
		||||
-- 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
 | 
			
		||||
-- 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
 | 
			
		||||
		end
 | 
			
		||||
	end
 | 
			
		||||
-- start to be execute by 4 seconds
 | 
			
		||||
 | 
			
		||||
	return highesti
 | 
			
		||||
end
 | 
			
		||||
local function globalstep_func(dtime)
 | 
			
		||||
	local actions = queue.actions
 | 
			
		||||
	-- split into two categories:
 | 
			
		||||
	-- actions_now: actions to execute now
 | 
			
		||||
	-- queue.actions: actions to execute later
 | 
			
		||||
	local actions_now = {}
 | 
			
		||||
	queue.actions = {}
 | 
			
		||||
 | 
			
		||||
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={}
 | 
			
		||||
 | 
			
		||||
	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
 | 
			
		||||
	for _, ac in ipairs(actions) do
 | 
			
		||||
		if ac.time > 0 then
 | 
			
		||||
			ac.time = ac.time - dtime -- executed later
 | 
			
		||||
			table.insert(mesecon.queue.actions, ac)
 | 
			
		||||
			-- action ac is to be executed later
 | 
			
		||||
			-- ~> insert into queue.actions
 | 
			
		||||
			ac.time = ac.time - dtime
 | 
			
		||||
			table.insert(queue.actions, ac)
 | 
			
		||||
		else
 | 
			
		||||
			-- action ac is to be executed now
 | 
			
		||||
			-- ~> insert into actions_now
 | 
			
		||||
			table.insert(actions_now, ac)
 | 
			
		||||
		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)
 | 
			
		||||
	-- stable-sort the executed actions after their priority
 | 
			
		||||
	-- some constructions might depend on the execution order, hence we first
 | 
			
		||||
	-- execute the actions that had a lower index in actions_now
 | 
			
		||||
	local old_action_order = {}
 | 
			
		||||
	for i, ac in ipairs(actions_now) do
 | 
			
		||||
		old_action_order[ac] = i
 | 
			
		||||
	end
 | 
			
		||||
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)
 | 
			
		||||
 | 
			
		||||
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,
 | 
			
		||||
	-- (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 +142,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)
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user