mesecons/mesecons/internal.lua
Jeija f1211f7dae Add ActionQueue priority system
This makes effectors nearer to the source of the action (the receptor) update first.

This defines behaviour for this piston circuit: http://i.imgur.com/9Pp2Mzb.png
And defines, that this memory circuit does not work from this direction: http://i.imgur.com/jJn0aFh.png
But it will work when using the switch from the other side: http://i.imgur.com/nvw0oZB.png

Only if two effectors have the same distance, there is nothing we can do about it, behaviour is not defined.
"Distance" is determined by the stack size of recursions in turnon / turnoff.
Priorities are between 0 (lowest) and 1 (highest).
2014-01-11 14:57:56 +01:00

649 lines
20 KiB
Lua

-- Internal.lua - The core of mesecons
--
-- For more practical developer resources see mesecons.tk
--
-- Function overview
-- mesecon:get_effector(nodename) --> Returns the mesecons.effector -specifictation in the nodedef by the nodename
-- mesecon:get_receptor(nodename) --> Returns the mesecons.receptor -specifictation in the nodedef by the nodename
-- mesecon:get_conductor(nodename) --> Returns the mesecons.conductor-specifictation in the nodedef by the nodename
-- mesecon:get_any_inputrules (node) --> Returns the rules of a node if it is a conductor or an effector
-- mesecon:get_any_outputrules (node) --> Returns the rules of a node if it is a conductor or a receptor
-- RECEPTORS
-- mesecon:is_receptor(nodename) --> Returns true if nodename is a receptor
-- mesecon:is_receptor_on(nodename) --> Returns true if nodename is an receptor with state = mesecon.state.on
-- mesecon:is_receptor_off(nodename) --> Returns true if nodename is an receptor with state = mesecon.state.off
-- mesecon:receptor_get_rules(node) --> Returns the rules of the receptor (mesecon.rules.default if none specified)
-- EFFECTORS
-- mesecon:is_effector(nodename) --> Returns true if nodename is an effector
-- mesecon:is_effector_on(nodename) --> Returns true if nodename is an effector with nodedef.mesecons.effector.action_off
-- mesecon:is_effector_off(nodename) --> Returns true if nodename is an effector with nodedef.mesecons.effector.action_on
-- mesecon:effector_get_rules(node) --> Returns the input rules of the effector (mesecon.rules.default if none specified)
-- SIGNALS
-- mesecon:activate(pos, node, recdepth) --> Activates the effector node at the specific pos (calls nodedef.mesecons.effector.action_on), higher recdepths are executed later
-- mesecon:deactivate(pos, node, recdepth) --> Deactivates the effector node at the specific pos (calls nodedef.mesecons.effector.action_off), "
-- mesecon:changesignal(pos, node, rulename, newstate) --> Changes the effector node at the specific pos (calls nodedef.mesecons.effector.action_change), "
-- RULES
-- mesecon:add_rules(name, rules) | deprecated? --> Saves rules table by name
-- mesecon:get_rules(name, rules) | deprecated? --> Loads rules table with name
-- CONDUCTORS
-- mesecon:is_conductor(nodename) --> Returns true if nodename is a conductor
-- mesecon:is_conductor_on(node) --> Returns true if node is a conductor with state = mesecon.state.on
-- mesecon:is_conductor_off(node) --> Returns true if node is a conductor with state = mesecon.state.off
-- mesecon:get_conductor_on(node_off) --> Returns the onstate nodename of the conductor
-- mesecon:get_conductor_off(node_on) --> Returns the offstate nodename of the conductor
-- mesecon:conductor_get_rules(node) --> Returns the input+output rules of a conductor (mesecon.rules.default if none specified)
-- HIGH-LEVEL Internals
-- mesecon:is_power_on(pos) --> Returns true if pos emits power in any way
-- mesecon:is_power_off(pos) --> Returns true if pos does not emit power in any way
-- mesecon:turnon(pos, rulename) --> Returns true whatever there is at pos. Calls itself for connected nodes (if pos is a conductor) --> recursive, the rulename is the name of the input rule that caused calling turnon; Uses third parameter recdepth internally to determine how far away the current node is from the initial pos as it uses recursion
-- mesecon:turnoff(pos, rulename) --> Turns off whatever there is at pos. Calls itself for connected nodes (if pos is a conductor) --> recursive, the rulename is the name of the input rule that caused calling turnoff; Uses third parameter recdepth internally to determine how far away the current node is from the initial pos as it uses recursion
-- mesecon:connected_to_receptor(pos) --> Returns true if pos is connected to a receptor directly or via conductors; calls itself if pos is a conductor --> recursive
-- mesecon:rules_link(output, input, dug_outputrules) --> Returns true if outputposition + outputrules = inputposition and inputposition + inputrules = outputposition (if the two positions connect)
-- mesecon:rules_link_anydir(outp., inp., d_outpr.) --> Same as rules mesecon:rules_link but also returns true if output and input are swapped
-- mesecon:is_powered(pos) --> Returns true if pos is powered by a receptor or a conductor
-- RULES ROTATION helpsers
-- mesecon:rotate_rules_right(rules)
-- mesecon:rotate_rules_left(rules)
-- mesecon:rotate_rules_up(rules)
-- mesecon:rotate_rules_down(rules)
-- These functions return rules that have been rotated in the specific direction
-- General
function mesecon:get_effector(nodename)
if minetest.registered_nodes[nodename]
and minetest.registered_nodes[nodename].mesecons
and minetest.registered_nodes[nodename].mesecons.effector then
return minetest.registered_nodes[nodename].mesecons.effector
end
end
function mesecon:get_receptor(nodename)
if minetest.registered_nodes[nodename]
and minetest.registered_nodes[nodename].mesecons
and minetest.registered_nodes[nodename].mesecons.receptor then
return minetest.registered_nodes[nodename].mesecons.receptor
end
end
function mesecon:get_conductor(nodename)
if minetest.registered_nodes[nodename]
and minetest.registered_nodes[nodename].mesecons
and minetest.registered_nodes[nodename].mesecons.conductor then
return minetest.registered_nodes[nodename].mesecons.conductor
end
end
function mesecon:get_any_outputrules (node)
if mesecon:is_conductor(node.name) then
return mesecon:conductor_get_rules(node)
elseif mesecon:is_receptor(node.name) then
return mesecon:receptor_get_rules(node)
end
return false
end
function mesecon:get_any_inputrules (node)
if mesecon:is_conductor(node.name) then
return mesecon:conductor_get_rules(node)
elseif mesecon:is_effector(node.name) then
return mesecon:effector_get_rules(node)
end
return false
end
-- Receptors
-- Nodes that can power mesecons
function mesecon:is_receptor_on(nodename)
local receptor = mesecon:get_receptor(nodename)
if receptor and receptor.state == mesecon.state.on then
return true
end
return false
end
function mesecon:is_receptor_off(nodename)
local receptor = mesecon:get_receptor(nodename)
if receptor and receptor.state == mesecon.state.off then
return true
end
return false
end
function mesecon:is_receptor(nodename)
local receptor = mesecon:get_receptor(nodename)
if receptor then
return true
end
return false
end
function mesecon:receptor_get_rules(node)
local receptor = mesecon:get_receptor(node.name)
if receptor then
local rules = receptor.rules
if type(rules) == 'function' then
return rules(node)
elseif rules then
return rules
end
end
return mesecon.rules.default
end
-- Effectors
-- Nodes that can be powered by mesecons
function mesecon:is_effector_on(nodename)
local effector = mesecon:get_effector(nodename)
if effector and effector.action_off then
return true
end
return false
end
function mesecon:is_effector_off(nodename)
local effector = mesecon:get_effector(nodename)
if effector and effector.action_on then
return true
end
return false
end
function mesecon:is_effector(nodename)
local effector = mesecon:get_effector(nodename)
if effector then
return true
end
return false
end
function mesecon:effector_get_rules(node)
local effector = mesecon:get_effector(node.name)
if effector then
local rules = effector.rules
if type(rules) == 'function' then
return rules(node)
elseif rules then
return rules
end
end
return mesecon.rules.default
end
-- #######################
-- # 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, recdepth)
if rulename == nil then
for _,rule in ipairs(mesecon:effector_get_rules(node)) do
mesecon:activate(pos, node, rule, recdepth + 1)
end
return
end
mesecon.queue:add_action(pos, "activate", {rulename}, nil, rulename, 1 / recdepth)
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, recdepth)
if rulename == nil then
for _,rule in ipairs(mesecon:effector_get_rules(node)) do
mesecon:deactivate(pos, node, rule, recdepth + 1)
end
return
end
mesecon.queue:add_action(pos, "deactivate", {rulename}, nil, rulename, 1 / recdepth)
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, recdepth)
if rulename == nil then
for _,rule in ipairs(mesecon:effector_get_rules(node)) do
mesecon:changesignal(pos, node, rule, newstate, recdepth + 1)
end
return
end
mesecon.queue:add_action(pos, "change", {rulename, newstate}, nil, rulename, 1 / recdepth)
end
-- #########
-- # Rules # "Database" for rulenames
-- #########
function mesecon:add_rules(name, rules)
mesecon.rules[name] = rules
end
function mesecon:get_rules(name)
return mesecon.rules[name]
end
-- Conductors
function mesecon:is_conductor_on(node, rulename)
local conductor = mesecon:get_conductor(node.name)
if conductor then
if conductor.state then
return conductor.state == mesecon.state.on
end
if conductor.states then
if not rulename then
return mesecon:getstate(node.name, conductor.states) ~= 1
end
local bit = mesecon:rule2bit(rulename, mesecon:conductor_get_rules(node))
local binstate = mesecon:getbinstate(node.name, conductor.states)
return mesecon:get_bit(binstate, bit)
end
end
return false
end
function mesecon:is_conductor_off(node, rulename)
local conductor = mesecon:get_conductor(node.name)
if conductor then
if conductor.state then
return conductor.state == mesecon.state.off
end
if conductor.states then
if not rulename then
return mesecon:getstate(node.name, conductor.states) == 1
end
local bit = mesecon:rule2bit(rulename, mesecon:conductor_get_rules(node))
local binstate = mesecon:getbinstate(node.name, conductor.states)
return not mesecon:get_bit(binstate, bit)
end
end
return false
end
function mesecon:is_conductor(nodename)
local conductor = mesecon:get_conductor(nodename)
if conductor then
return true
end
return false
end
function mesecon:get_conductor_on(node_off, rulename)
local conductor = mesecon:get_conductor(node_off.name)
if conductor then
if conductor.onstate then
return conductor.onstate
end
if conductor.states then
local bit = mesecon:rule2bit(rulename, mesecon:conductor_get_rules(node_off))
local binstate = mesecon:getbinstate(node_off.name, conductor.states)
binstate = mesecon:set_bit(binstate, bit, "1")
return conductor.states[tonumber(binstate,2)+1]
end
end
return offstate
end
function mesecon:get_conductor_off(node_on, rulename)
local conductor = mesecon:get_conductor(node_on.name)
if conductor then
if conductor.offstate then
return conductor.offstate
end
if conductor.states then
local bit = mesecon:rule2bit(rulename, mesecon:conductor_get_rules(node_on))
local binstate = mesecon:getbinstate(node_on.name, conductor.states)
binstate = mesecon:set_bit(binstate, bit, "0")
return conductor.states[tonumber(binstate,2)+1]
end
end
return onstate
end
function mesecon:conductor_get_rules(node)
local conductor = mesecon:get_conductor(node.name)
if conductor then
local rules = conductor.rules
if type(rules) == 'function' then
return rules(node)
elseif rules then
return rules
end
end
return mesecon.rules.default
end
-- some more general high-level stuff
function mesecon:is_power_on(pos, rulename)
local node = minetest.get_node(pos)
if mesecon:is_conductor_on(node, rulename) or mesecon:is_receptor_on(node.name) then
return true
end
return false
end
function mesecon:is_power_off(pos, rulename)
local node = minetest.get_node(pos)
if mesecon:is_conductor_off(node, rulename) or mesecon:is_receptor_off(node.name) then
return true
end
return false
end
function mesecon:turnon(pos, rulename, recdepth)
recdepth = recdepth or 2
local node = minetest.get_node(pos)
if mesecon:is_conductor_off(node, rulename) then
local rules = mesecon:conductor_get_rules(node)
if not rulename then
for _, rule in ipairs(mesecon:flattenrules(rules)) do
if mesecon:connected_to_receptor(pos, rule) then
mesecon:turnon(pos, rule, recdepth + 1)
end
end
return
end
minetest.swap_node(pos, {name = mesecon:get_conductor_on(node, rulename), param2 = node.param2})
for _, rule in ipairs(mesecon:rule2meta(rulename, 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, recdepth + 1)
end
end
elseif mesecon:is_effector(node.name) then
mesecon:changesignal(pos, node, rulename, mesecon.state.on, recdepth)
if mesecon:is_effector_off(node.name) then
mesecon:activate(pos, node, rulename, recdepth)
end
end
end
function mesecon:turnoff(pos, rulename, recdepth)
recdepth = recdepth or 0
local node = minetest.get_node(pos)
if mesecon:is_conductor_on(node, rulename) then
local rules = mesecon:conductor_get_rules(node)
minetest.swap_node(pos, {name = mesecon:get_conductor_off(node, rulename), param2 = node.param2})
for _, rule in ipairs(mesecon:rule2meta(rulename, rules)) do
local np = mesecon:addPosRule(pos, rule)
local rulenames = mesecon:rules_link_rule_all(pos, rule)
for _, rulename in ipairs(rulenames) do
mesecon:turnoff(np, rulename, recdepth + 1)
end
end
elseif mesecon:is_effector(node.name) then
mesecon:changesignal(pos, node, rulename, mesecon.state.off, recdepth)
if mesecon:is_effector_on(node.name)
and not mesecon:is_powered(pos) then
mesecon:deactivate(pos, node, rulename, recdepth + 1)
end
end
end
function mesecon:connected_to_receptor(pos, rulename)
local node = minetest.get_node(pos)
-- Check if conductors around are connected
local rules = mesecon:get_any_inputrules(node)
if not rules then return false end
for _, rule in ipairs(mesecon:rule2meta(rulename, rules)) do
local np = mesecon:addPosRule(pos, rule)
if mesecon:rules_link(np, pos) then
if mesecon:find_receptor_on(np, {}, mesecon:invertRule(rule)) then
return true
end
end
end
return false
end
function mesecon:find_receptor_on(pos, checked, rulename)
local node = minetest.get_node(pos)
if mesecon:is_receptor_on(node.name) then
-- add current position to checked
table.insert(checked, {x=pos.x, y=pos.y, z=pos.z})
return true
end
if mesecon:is_conductor(node.name) then
local rules = mesecon:conductor_get_rules(node)
local metaindex = mesecon:rule2metaindex(rulename, rules)
-- find out if node has already been checked (to prevent from endless loop)
for _, cp in ipairs(checked) do
if mesecon:cmpPos(cp, pos) and cp.metaindex == metaindex then
return false, checked
end
end
-- add current position to checked
table.insert(checked, {x=pos.x, y=pos.y, z=pos.z, metaindex = metaindex})
for _, rule in ipairs(mesecon:rule2meta(rulename, rules)) do
local np = mesecon:addPosRule(pos, rule)
if mesecon:rules_link(np, pos) then
if mesecon:find_receptor_on(np, checked, mesecon:invertRule(rule)) then
return true
end
end
end
else
-- find out if node has already been checked (to prevent from endless loop)
for _, cp in ipairs(checked) do
if mesecon:cmpPos(cp, pos) then
return false, checked
end
end
table.insert(checked, {x=pos.x, y=pos.y, z=pos.z})
end
return false
end
function mesecon:rules_link(output, input, dug_outputrules) --output/input are positions (outputrules optional, used if node has been dug), second return value: the name of the affected input rule
local outputnode = minetest.get_node(output)
local inputnode = minetest.get_node(input)
local outputrules = dug_outputrules or mesecon:get_any_outputrules (outputnode)
local inputrules = mesecon:get_any_inputrules (inputnode)
if not outputrules or not inputrules then
return
end
for _, outputrule in ipairs(mesecon:flattenrules(outputrules)) do
-- Check if output sends to input
if mesecon:cmpPos(mesecon:addPosRule(output, outputrule), input) then
for _, inputrule in ipairs(mesecon:flattenrules(inputrules)) do
-- Check if input accepts from output
if mesecon:cmpPos(mesecon:addPosRule(input, inputrule), output) then
if inputrule.sx == nil or outputrule.sx == nil or mesecon:cmpSpecial(inputrule, outputrule) then
return true, inputrule
end
end
end
end
end
return false
end
function mesecon:rules_link_rule_all(output, rule) --output/input are positions (outputrules optional, used if node has been dug), second return value: affected input rules
local input = mesecon:addPosRule(output, rule)
local inputnode = minetest.get_node(input)
local inputrules = mesecon:get_any_inputrules (inputnode)
if not inputrules then
return {}
end
local rules = {}
for _, inputrule in ipairs(mesecon:flattenrules(inputrules)) do
-- Check if input accepts from output
if mesecon:cmpPos(mesecon:addPosRule(input, inputrule), output) then
if inputrule.sx == nil or rule.sx == nil or mesecon:cmpSpecial(inputrule, rule) then
rules[#rules+1] = inputrule
end
end
end
return rules
end
function mesecon:rules_link_anydir(pos1, pos2)
return mesecon:rules_link(pos1, pos2) or mesecon:rules_link(pos2, pos1)
end
function mesecon:is_powered(pos, rule)
local node = minetest.get_node(pos)
local rules = mesecon:get_any_inputrules(node)
if not rules then return false end
if not rule then
for _, rule in ipairs(mesecon:flattenrules(rules)) do
local np = mesecon:addPosRule(pos, rule)
local nn = minetest.get_node(np)
if (mesecon:is_conductor_on (nn, mesecon:invertRule(rule)) or mesecon:is_receptor_on (nn.name))
and mesecon:rules_link(np, pos) then
return true
end
end
else
local np = mesecon:addPosRule(pos, rule)
local nn = minetest.get_node(np)
if (mesecon:is_conductor_on (nn, mesecon:invertRule(rule)) or mesecon:is_receptor_on (nn.name))
and mesecon:rules_link(np, pos) then
return true
end
end
return false
end
--Rules rotation Functions:
function mesecon:rotate_rules_right(rules)
local nr = {}
for i, rule in ipairs(rules) do
if rule.sx then
table.insert(nr, {
x = -rule.z,
y = rule.y,
z = rule.x,
sx = -rule.sz,
sy = rule.sy,
sz = rule.sx})
else
table.insert(nr, {
x = -rule.z,
y = rule.y,
z = rule.x})
end
end
return nr
end
function mesecon:rotate_rules_left(rules)
local nr = {}
for i, rule in ipairs(rules) do
if rule.sx then
table.insert(nr, {
x = rule.z,
y = rule.y,
z = -rule.x,
sx = rule.sz,
sy = rule.sy,
sz = -rule.sx})
else
table.insert(nr, {
x = rule.z,
y = rule.y,
z = -rule.x})
end
end
return nr
end
function mesecon:rotate_rules_down(rules)
local nr = {}
for i, rule in ipairs(rules) do
if rule.sx then
table.insert(nr, {
x = -rule.y,
y = rule.x,
z = rule.z,
sx = -rule.sy,
sy = rule.sx,
sz = rule.sz})
else
table.insert(nr, {
x = -rule.y,
y = rule.x,
z = rule.z})
end
end
return nr
end
function mesecon:rotate_rules_up(rules)
local nr = {}
for i, rule in ipairs(rules) do
if rule.sx then
table.insert(nr, {
x = rule.y,
y = -rule.x,
z = rule.z,
sx = rule.sy,
sy = -rule.sx,
sz = rule.sz})
else
table.insert(nr, {
x = rule.y,
y = -rule.x,
z = rule.z})
end
end
return nr
end