Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Merge branch 'actionqueue'
This introduces the ActionQueue, a new kind of MESECONS_GLOBALSTEP.
Circuits using delayers will now resume when restarting the server.
Also, large circuits should automatically resume if parts of them are
in unloaded chunks.
Old circuits e.g. using gates will not resume when mesecons is updated,
which means you have to restart them once. But after that, it should work
just like it used to.
This will fix a lot of stuff but may also introduce some new bugs.
So please report them!
  • Loading branch information
Jeija committed Jan 19, 2014
2 parents 7517cc4 + a632a8a commit 8a71f51
Show file tree
Hide file tree
Showing 11 changed files with 396 additions and 222 deletions.
119 changes: 119 additions & 0 deletions mesecons/actionqueue.lua
@@ -0,0 +1,119 @@
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
-- priority specifies the order actions are executed within one globalstep, highest by default
-- should be between 0 and 1
function mesecon.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
action = { pos=mesecon:tablecopy(pos),
func=func,
params=mesecon:tablecopy(params),
time=time,
owcheck=(overwritecheck and mesecon:tablecopy(overwritecheck)) or nil,
priority=priority}

-- if not using the queue, (MESECONS_GLOBALSTEP off), just execute the function an we're done
if not MESECONS_GLOBALSTEP and action.time == 0 then
mesecon.queue:execute(action)
return
end

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(mesecon:cmpPos(pos, ac.pos)
and mesecon:cmpAny(overwritecheck, ac.owcheck)) then
toremove = i
break
end
end
end

if (toremove ~= nil) then
table.remove(mesecon.queue.actions, toremove)
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 get_highest_priority = function (actions)
local highestp = 0, highesti
for i, ac in ipairs(actions) do
if ac.priority > highestp then
highestp = ac.priority
highesti = i
end
end

return highesti
end

local m_time = 0
minetest.register_globalstep(function (dtime)
m_time = m_time + dtime
if (m_time < MESECONS_RESUMETIME) then return end -- don't even try if server has not been running for XY seconds
local actions = mesecon:tablecopy(mesecon.queue.actions)
local actions_now={}

mesecon.queue.actions = {}

-- sort actions in execute now (actions_now) and for 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)
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)
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)
86 changes: 38 additions & 48 deletions mesecons/init.lua
Expand Up @@ -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")
Expand All @@ -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
Expand All @@ -101,49 +76,64 @@ 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

-- if area (any of the rule targets) is not loaded, keep trying and call this again later
if MESECONS_GLOBALSTEP then -- trying to enable resuming with globalstep disabled would cause an endless loop
for _, rule in ipairs(mesecon:flattenrules(rules)) do
local np = mesecon:addPosRule(pos, rule)
-- if area is not loaded, keep trying
if minetest.get_node_or_nil(np) == nil then
mesecon.queue:add_action(pos, "receptor_on", {rules}, nil, rules)
return
end
end
end

-- execute action
for _, rule in ipairs(mesecon:flattenrules(rules)) do
local np = mesecon:addPosRule(pos, rule)
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}, nil, rules)
end

function mesecon:receptor_off_i(pos, rules)
mesecon.queue:add_function("receptor_off", function (pos, rules)
rules = rules or mesecon.rules.default

-- if area (any of the rule targets) is not loaded, keep trying and call this again later
if MESECONS_GLOBALSTEP then
for _, rule in ipairs(mesecon:flattenrules(rules)) do
local np = mesecon:addPosRule(pos, rule)
if minetest.get_node_or_nil(np) == nil then
mesecon.queue:add_action(pos, "receptor_off", {rules}, nil, rules)
return
end
end
end

for _, rule in ipairs(mesecon:flattenrules(rules)) do
local np = mesecon:addPosRule(pos, rule)
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
mesecon:turnoff(np, rulename)
else
mesecon:changesignal(np, minetest.get_node(np), rulename, mesecon.state.off)
mesecon:changesignal(np, minetest.get_node(np), rulename, mesecon.state.off, 2)
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}, nil, rules)
end


Expand Down

0 comments on commit 8a71f51

Please sign in to comment.