Improve the LuaController

Changes:
  * Stops code after a certain number of instructions.
  * Allows functions, due to instruction counting.
  * Allows loops and goto with non-JIT Lua (LuaJIT doesn't count looping as an instruction, allowing infinite loops), due to instruction counting.
  * Removes string matching functions as they can be slow.
  * Adds some safe functions.
  * Limits the amount of printing that can be done (to prevent console flooding).
  * Code cleanup.
  * More...
This commit is contained in:
ShadowNinja 2014-08-06 22:54:17 -04:00
parent d325292291
commit bd1766e448
3 changed files with 439 additions and 370 deletions

View File

@ -1,18 +1,13 @@
minetest.swap_node = minetest.swap_node or function(pos, node) local rules = {
local data = minetest.get_meta(pos):to_table() a = {x = -1, y = 0, z = 0, name="A"},
minetest.add_node(pos, node) b = {x = 0, y = 0, z = 1, name="B"},
minetest.get_meta(pos):from_table(data) c = {x = 1, y = 0, z = 0, name="C"},
end d = {x = 0, y = 0, z = -1, name="D"},
}
local rules = {} function mesecon.legacy_update_ports(pos)
rules.a = {x = -1, y = 0, z = 0, name="A"}
rules.b = {x = 0, y = 0, z = 1, name="B"}
rules.c = {x = 1, y = 0, z = 0, name="C"}
rules.d = {x = 0, y = 0, z = -1, name="D"}
function legacy_update_ports(pos)
local meta = minetest.get_meta(pos) local meta = minetest.get_meta(pos)
L = { local ports = {
a = mesecon:is_power_on(mesecon:addPosRule(pos, rules.a), a = mesecon:is_power_on(mesecon:addPosRule(pos, rules.a),
mesecon:invertRule(rules.a)) and mesecon:invertRule(rules.a)) and
mesecon:rules_link(mesecon:addPosRule(pos, rules.a), pos), mesecon:rules_link(mesecon:addPosRule(pos, rules.a), pos),
@ -26,7 +21,12 @@ function legacy_update_ports(pos)
mesecon:invertRule(rules.d)) and mesecon:invertRule(rules.d)) and
mesecon:rules_link(mesecon:addPosRule(pos, rules.d), pos), mesecon:rules_link(mesecon:addPosRule(pos, rules.d), pos),
} }
local n = (L.a and 1 or 0) + (L.b and 2 or 0) + (L.c and 4 or 0) + (L.d and 8 or 0) + 1 local n =
(ports.a and 1 or 0) +
(ports.b and 2 or 0) +
(ports.c and 4 or 0) +
(ports.d and 8 or 0) + 1
meta:set_int("real_portstates", n) meta:set_int("real_portstates", n)
return L return ports
end end

View File

@ -1,11 +1,21 @@
-- ______
-- |
-- |
-- | __ ___ _ __ _ _
-- | | | | | |\ | | |_| | | | | |_ |_|
-- |___| |______ |__| | \| | | \ |__| |_ |_ |_ |\
-- |
-- |
--
-- Reference -- Reference
-- ports = get_real_portstates(pos): gets if inputs are powered from outside -- ports = get_real_port_states(pos): gets if inputs are powered from outside
-- newport = merge_portstates(state1, state2): just does result = state1 or state2 for every port -- newport = merge_port_states(state1, state2): just does result = state1 or state2 for every port
-- action_setports(pos, rule, state): activates/deactivates the mesecons according to the portstates (helper for action) -- set_port(pos, rule, state): activates/deactivates the mesecons according to the port states
-- action(pos, ports): Applies new portstates to a luacontroller at pos -- set_port_states(pos, ports): Applies new port states to a LuaController at pos
-- lc_update(pos): updates the controller at pos by executing the code -- run(pos): runs the code in the controller at pos
-- reset_meta (pos, code, errmsg): performs a software-reset, installs new code and prints error messages -- reset_meta(pos, code, errmsg): performs a software-reset, installs new code and prints error messages
-- reset (pos): performs a hardware reset, turns off all ports -- resetn(pos): performs a hardware reset, turns off all ports
-- --
-- The Sandbox -- The Sandbox
-- The whole code of the controller runs in a sandbox, -- The whole code of the controller runs in a sandbox,
@ -20,70 +30,85 @@
local BASENAME = "mesecons_luacontroller:luacontroller" local BASENAME = "mesecons_luacontroller:luacontroller"
local rules = {} local rules = {
rules.a = {x = -1, y = 0, z = 0, name="A"} a = {x = -1, y = 0, z = 0, name="A"},
rules.b = {x = 0, y = 0, z = 1, name="B"} b = {x = 0, y = 0, z = 1, name="B"},
rules.c = {x = 1, y = 0, z = 0, name="C"} c = {x = 1, y = 0, z = 0, name="C"},
rules.d = {x = 0, y = 0, z = -1, name="D"} d = {x = 0, y = 0, z = -1, name="D"},
}
------------------ ------------------
-- Action stuff -- -- Action stuff --
------------------ ------------------
-- These helpers are required to set the portstates of the luacontroller -- These helpers are required to set the port states of the luacontroller
function lc_update_real_portstates(pos, rulename, newstate) local function update_real_port_states(pos, rule_name, new_state)
local meta = minetest.get_meta(pos) local meta = minetest.get_meta(pos)
if rulename == nil then if rule_name == nil then
meta:set_int("real_portstates", 1) meta:set_int("real_portstates", 1)
return return
end end
local n = meta:get_int("real_portstates") - 1 local n = meta:get_int("real_portstates") - 1
if n < 0 then if n < 0 then
legacy_update_ports(pos) mesecon.legacy_update_ports(pos)
n = meta:get_int("real_portstates") - 1 n = meta:get_int("real_portstates") - 1
end end
-- Create list of bytes in n
local L = {} local L = {}
for i = 1, 4 do for i = 1, 4 do
L[i] = n%2 L[i] = n % 2
n = math.floor(n/2) n = math.floor(n / 2)
end end
if rulename.x == nil then -- (0,-1) (-1,0) (1,0) (0,1)
for _, rname in ipairs(rulename) do local pos_to_side = { 4, 1, nil, 3, 2 }
local port = ({4, 1, nil, 3, 2})[rname.x+2*rname.z+3] if rule_name.x == nil then
for _, rname in ipairs(rule_name) do
local port = pos_to_side[rname.x + (2 * rname.z) + 3]
L[port] = (newstate == "on") and 1 or 0 L[port] = (newstate == "on") and 1 or 0
end end
else else
local port = ({4, 1, nil, 3, 2})[rulename.x+2*rulename.z+3] local port = pos_to_side[rule_name.x + (2 * rule_name.z) + 3]
L[port] = (newstate == "on") and 1 or 0 L[port] = (new_state == "on") and 1 or 0
end end
meta:set_int("real_portstates", 1 + L[1] + 2*L[2] + 4*L[3] + 8*L[4]) meta:set_int("real_portstates",
1 +
1 * L[1] +
2 * L[2] +
4 * L[3] +
8 * L[4])
end end
local get_real_portstates = function(pos) -- determine if ports are powered (by itself or from outside)
local port_names = {"a", "b", "c", "d"}
local function get_real_port_states(pos)
-- Determine if ports are powered (by itself or from outside)
local meta = minetest.get_meta(pos) local meta = minetest.get_meta(pos)
local L = {} local L = {}
local n = meta:get_int("real_portstates") - 1 local n = meta:get_int("real_portstates") - 1
if n < 0 then if n < 0 then
return legacy_update_ports(pos) return mesecon.legacy_update_ports(pos)
end end
for _, index in ipairs({"a", "b", "c", "d"}) do for _, name in ipairs(port_names) do
L[index] = ((n%2) == 1) L[name] = ((n % 2) == 1)
n = math.floor(n/2) n = math.floor(n / 2)
end end
return L return L
end end
local merge_portstates = function (ports, vports)
local npo = {a=false, b=false, c=false, d=false} local function merge_port_states(ports, vports)
npo.a = vports.a or ports.a return {
npo.b = vports.b or ports.b a = ports.a or vports.a,
npo.c = vports.c or ports.c b = ports.b or vports.b,
npo.d = vports.d or ports.d c = ports.c or vports.c,
return npo d = ports.d or vports.d,
}
end end
local generate_name = function (ports)
local overwrite = overwrite or {} local function generate_name(ports)
local d = ports.d and 1 or 0 local d = ports.d and 1 or 0
local c = ports.c and 1 or 0 local c = ports.c and 1 or 0
local b = ports.b and 1 or 0 local b = ports.b and 1 or 0
@ -91,7 +116,8 @@ local generate_name = function (ports)
return BASENAME..d..c..b..a return BASENAME..d..c..b..a
end end
local setport = function (pos, rule, state)
local function set_port(pos, rule, state)
if state then if state then
mesecon:receptor_on(pos, {rule}) mesecon:receptor_on(pos, {rule})
else else
@ -99,93 +125,114 @@ local setport = function (pos, rule, state)
end end
end end
local action = function (pos, ports)
local function clean_port_states(ports)
ports.a = ports.a and true or false
ports.b = ports.b and true or false
ports.c = ports.c and true or false
ports.d = ports.d and true or false
end
local function set_port_states(pos, ports)
local node = minetest.get_node(pos) local node = minetest.get_node(pos)
local name = node.name local name = node.name
clean_port_states(ports)
local vports = minetest.registered_nodes[name].virtual_portstates local vports = minetest.registered_nodes[name].virtual_portstates
local newname = generate_name(ports) local new_name = generate_name(ports)
if name ~= newname and vports then if name ~= new_name and vports then
local rules_on = {} minetest.swap_node(pos, {name = new_name, param2 = node.param2})
local rules_off = {}
minetest.swap_node(pos, {name = newname, param2 = node.param2}) if ports.a ~= vports.a then set_port(pos, rules.a, ports.a) end
if ports.b ~= vports.b then set_port(pos, rules.b, ports.b) end
if ports.a ~= vports.a then setport(pos, rules.a, ports.a) end if ports.c ~= vports.c then set_port(pos, rules.c, ports.c) end
if ports.b ~= vports.b then setport(pos, rules.b, ports.b) end if ports.d ~= vports.d then set_port(pos, rules.d, ports.d) end
if ports.c ~= vports.c then setport(pos, rules.c, ports.c) end
if ports.d ~= vports.d then setport(pos, rules.d, ports.d) end
end end
end end
--------------------
-- Overheat stuff --
--------------------
local overheat_off = function(pos) -----------------
-- Overheating --
-----------------
local function overheat_off(pos)
mesecon:receptor_off(pos, mesecon.rules.flat) mesecon:receptor_off(pos, mesecon.rules.flat)
end end
-------------------
-- Parsing stuff --
-------------------
local code_prohibited = function(code) local function overheat(pos, meta)
-- Clean code if mesecon.do_overheat(pos) then -- If too hot
local prohibited = {"while", "for", "repeat", "until", "function", "goto"} local node = minetest.get_node(pos)
for _, p in ipairs(prohibited) do node.name = BASENAME.."_burnt"
if string.find(code, p) then minetest.swap_node(pos, node)
return "Prohibited command: "..p -- Wait for pending operations
end minetest.after(0.2, overheat_off, pos)
return true
end end
end end
local safe_print = function(param)
print(dump(param))
end
deep_copy = function(original, visited) --deep copy that removes functions -------------------------
visited = visited or {} -- Parsing and running --
if visited[original] ~= nil then --already visited this node -------------------------
return visited[original]
-- Limit printing to prevent flooding
local print_count = 0
local function safe_print(param)
local to_print = dump(param, "")
print_count = print_count + 1 + (#to_print / 64)
for c in to_print:gmatch("\n") do
print_count = print_count + 1
end end
if type(original) == 'table' then --nested table if print_count > 8 then
local copy = {} error("Too much printing!")
visited[original] = copy
for key, value in next, original, nil do
copy[deep_copy(key, visited)] = deep_copy(value, visited)
end
setmetatable(copy, deep_copy(getmetatable(original), visited))
return copy
elseif type(original) == 'function' then --ignore functions
return nil
else --by-value type
return original
end end
print(to_print)
end end
local safe_serialize = function(value) minetest.register_globalstep(function(dtime)
return minetest.serialize(deep_copy(value)) print_count = print_count - dtime
end
mesecon.queue:add_function("lc_interrupt", function (pos, iid, luac_id)
-- There is no luacontroller anymore / it has been reprogrammed / replaced
if (minetest.get_meta(pos):get_int("luac_id") ~= luac_id) then return end
lc_update(pos, {type="interrupt", iid = iid})
end) end)
local getinterrupt = function(pos)
local interrupt = function (time, iid) -- iid = interrupt id local function remove_functions(x)
local tp = type(x)
if tp == "table" then
for key, value in pairs(x) do
local key_t, val_t = type(key), type(value)
if key_t == "function" or val_t == "function" then
x[key] = nil
else
if key_t == "table" then
remove_functions(key)
end
if val_t == "table" then
remove_functions(value)
end
end
end
elseif tp == "function" then
return nil
end
return x
end
local function get_interrupt(pos)
-- iid = interrupt id
local function interrupt(time, iid)
if type(time) ~= "number" then return end if type(time) ~= "number" then return end
luac_id = minetest.get_meta(pos):get_int("luac_id") local luac_id = minetest.get_meta(pos):get_int("luac_id")
mesecon.queue:add_action(pos, "lc_interrupt", {iid, luac_id}, time, iid, 1) mesecon.queue:add_action(pos, "LuaController interrupt", {iid, luac_id}, time, iid, 1)
end end
return interrupt return interrupt
end end
local getdigiline_send = function(pos)
local function get_digiline_send(pos)
if not digiline then return end if not digiline then return end
-- Send messages on next serverstep
return function(channel, msg) return function(channel, msg)
minetest.after(0, function() minetest.after(0, function()
digiline:receptor_send(pos, digiline.rules.default, channel, msg) digiline:receptor_send(pos, digiline.rules.default, channel, msg)
@ -193,145 +240,180 @@ local getdigiline_send = function(pos)
end end
end end
local create_environment = function(pos, mem, event)
local safe_globals = {
"assert", "error", "ipairs", "next", "pairs", "pcall", "select",
"tonumber", "tostring", "type", "unpack", "_VERSION", "xpcall",
}
local function create_environment(pos, mem, event)
-- Gather variables for the environment -- Gather variables for the environment
local vports = minetest.registered_nodes[minetest.get_node(pos).name].virtual_portstates local vports = minetest.registered_nodes[minetest.get_node(pos).name].virtual_portstates
vports = {a = vports.a, b = vports.b, c = vports.c, d = vports.d} local vports_copy = {}
local rports = get_real_portstates(pos) for k, v in pairs(vports) do vports_copy[k] = v end
local rports = get_real_port_states(pos)
return { -- Create new library tables on each call to prevent one LuaController
print = safe_print, -- from breaking a library and messing up other LuaControllers.
pin = merge_portstates(vports, rports), local env = {
port = vports, pin = merge_port_states(vports, rports),
interrupt = getinterrupt(pos), port = vports_copy,
digiline_send = getdigiline_send(pos), event = event,
mem = mem, mem = mem,
tostring = tostring, heat = minetest.get_meta(pos):get_int("heat"),
tonumber = tonumber, heat_max = OVERHEAT_MAX,
heat = minetest.get_meta(pos):get_int("heat"), print = safe_print,
heat_max = OVERHEAT_MAX, interrupt = get_interrupt(pos),
string = { digiline_send = get_digiline_send(pos),
byte = string.byte, string = {
char = string.char, byte = string.byte,
find = string.find, char = string.char,
format = string.format, format = string.format,
gmatch = string.gmatch, gsub = string.gsub,
gsub = string.gsub, len = string.len,
len = string.len, lower = string.lower,
lower = string.lower, upper = string.upper,
upper = string.upper, rep = string.rep,
match = string.match, reverse = string.reverse,
rep = string.rep, sub = string.sub,
reverse = string.reverse, },
sub = string.sub, math = {
}, abs = math.abs,
math = { acos = math.acos,
abs = math.abs, asin = math.asin,
acos = math.acos, atan = math.atan,
asin = math.asin, atan2 = math.atan2,
atan = math.atan, ceil = math.ceil,
atan2 = math.atan2, cos = math.cos,
ceil = math.ceil, cosh = math.cosh,
cos = math.cos, deg = math.deg,
cosh = math.cosh, exp = math.exp,
deg = math.deg, floor = math.floor,
exp = math.exp, fmod = math.fmod,
floor = math.floor, frexp = math.frexp,
fmod = math.fmod, huge = math.huge,
frexp = math.frexp, ldexp = math.ldexp,
huge = math.huge, log = math.log,
ldexp = math.ldexp, log10 = math.log10,
log = math.log, max = math.max,
log10 = math.log10, min = math.min,
max = math.max, modf = math.modf,
min = math.min, pi = math.pi,
modf = math.modf, pow = math.pow,
pi = math.pi, rad = math.rad,
pow = math.pow, random = math.random,
rad = math.rad, sin = math.sin,
random = math.random, sinh = math.sinh,
sin = math.sin, sqrt = math.sqrt,
sinh = math.sinh, tan = math.tan,
sqrt = math.sqrt, tanh = math.tanh,
tan = math.tan, },
tanh = math.tanh, table = {
}, concat = table.concat,
table = { insert = table.insert,
insert = table.insert, maxn = table.maxn,
maxn = table.maxn, remove = table.remove,
remove = table.remove, sort = table.sort,
sort = table.sort },
}, os = {
event = event, clock = os.clock,
difftime = os.difftime,
time = os.time,
},
} }
env._G = env
for _, name in pairs(safe_globals) do
env[name] = _G[name]
end
return env
end end
local create_sandbox = function (code, env)
-- Create Sandbox local function timeout()
debug.sethook() -- Clear hook
error("Code timed out!")
end
local function code_prohibited(code)
-- LuaJIT doesn't increment the instruction counter when running
-- loops, so we have to sanitize inputs if we're using LuaJIT.
if not jit then
return false
end
local prohibited = {"while", "for", "do", "repeat", "until", "goto"}
code = " "..code.." "
for _, p in ipairs(prohibited) do
if string.find(code, "[^%w_]"..p.."[^%w_]") then
return "Prohibited command: "..p
end
end
end
local function create_sandbox(code, env)
if code:byte(1) == 27 then if code:byte(1) == 27 then
return _, "You Hacker You! Don't use binary code!" return nil, "Binary code prohibited."
end end
f, msg = loadstring(code) local f, msg = loadstring(code)
if not f then return _, msg end if not f then return nil, msg end
setfenv(f, env) setfenv(f, env)
return f
end
local lc_overheat = function (pos, meta) return function(...)
if mesecon.do_overheat(pos) then -- if too hot debug.sethook(timeout, "", 10000)
local node = minetest.get_node(pos) local ok, ret = pcall(f, ...)
minetest.swap_node(pos, {name = BASENAME.."_burnt", param2 = node.param2}) debug.sethook() -- Clear hook
minetest.after(0.2, overheat_off, pos) -- wait for pending operations if not ok then error(ret) end
return true return ret
end end
end end
local load_memory = function(meta)
local function load_memory(meta)
return minetest.deserialize(meta:get_string("lc_memory")) or {} return minetest.deserialize(meta:get_string("lc_memory")) or {}
end end
local save_memory = function(meta, mem)
meta:set_string("lc_memory", safe_serialize(mem)) local function save_memory(meta, mem)
meta:set_string("lc_memory",
minetest.serialize(
remove_functions(mem)
)
)
end end
local ports_invalid = function (var)
if type(var) == "table" then
return false
end
return "The ports you set are invalid"
end
---------------------- local function run(pos, event)
-- Parsing function --
----------------------
lc_update = function (pos, event)
local meta = minetest.get_meta(pos) local meta = minetest.get_meta(pos)
if lc_overheat(pos) then return end if overheat(pos) then return end
-- load code & mem from memory -- Load code & mem from meta
local mem = load_memory(meta) local mem = load_memory(meta)
local code = meta:get_string("code") local code = meta:get_string("code")
-- make sure code is ok and create environment local err = code_prohibited(code)
local prohibited = code_prohibited(code) if err then return err end
if prohibited then return prohibited end
-- Create environment
local env = create_environment(pos, mem, event) local env = create_environment(pos, mem, event)
-- create the sandbox and execute code -- Create the sandbox and execute code
local chunk, msg = create_sandbox (code, env) local f, msg = create_sandbox(code, env)
if not chunk then return msg end if not f then return msg end
local success, msg = pcall(f) local success, msg = pcall(f)
if not success then return msg end if not success then return msg end
if ports_invalid(env.port) then return ports_invalid(env.port) end if type(env.port) ~= "table" then
return "Ports set are invalid."
end
save_memory(meta, mem) save_memory(meta, env.mem)
-- Actually set the ports -- Actually set the ports
action(pos, env.port) set_port_states(pos, env.port)
end end
local reset_meta = function(pos, code, errmsg) local function reset_meta(pos, code, errmsg)
local meta = minetest.get_meta(pos) local meta = minetest.get_meta(pos)
meta:set_string("code", code) meta:set_string("code", code)
code = minetest.formspec_escape(code or "") code = minetest.formspec_escape(code or "")
@ -343,172 +425,163 @@ local reset_meta = function(pos, code, errmsg)
"image_button_exit[9.72,-0.25;0.425,0.4;jeija_close_window.png;exit;]".. "image_button_exit[9.72,-0.25;0.425,0.4;jeija_close_window.png;exit;]"..
"label[0.1,5;"..errmsg.."]") "label[0.1,5;"..errmsg.."]")
meta:set_int("heat", 0) meta:set_int("heat", 0)
meta:set_int("luac_id", math.random(1, 1000000)) meta:set_int("luac_id", math.random(1, 65535))
end end
local reset = function (pos) local function reset(pos)
action(pos, {a=false, b=false, c=false, d=false}) set_port_states(pos, {a=false, b=false, c=false, d=false})
end end
-- ______
-- | mesecon.queue:add_function("LuaController interrupt", function(pos, iid, luac_id)
-- | -- There is no LuaController anymore / it has been reprogrammed / replaced
-- | __ ___ _ __ _ _ if minetest.get_meta(pos):get_int("luac_id") ~= luac_id then
-- | | | | | |\ | | |_| | | | | |_ |_| return
-- |___| |______ |__| | \| | | \ |__| |_ |_ |_ |\ end
-- | run(pos, {type="interrupt", iid = iid})
-- | end)
--
----------------------- -----------------------
-- Node Registration -- -- Node Registration --
----------------------- -----------------------
local output_rules={} local output_rules = {}
local input_rules={} local input_rules = {}
local nodebox = { local node_box = {
type = "fixed", type = "fixed",
fixed = { fixed = {
{ -8/16, -8/16, -8/16, 8/16, -7/16, 8/16 }, -- bottom slab {-8/16, -8/16, -8/16, 8/16, -7/16, 8/16}, -- Bottom slab
{ -5/16, -7/16, -5/16, 5/16, -6/16, 5/16 }, -- circuit board {-5/16, -7/16, -5/16, 5/16, -6/16, 5/16}, -- Circuit board
{ -3/16, -6/16, -3/16, 3/16, -5/16, 3/16 }, -- IC {-3/16, -6/16, -3/16, 3/16, -5/16, 3/16}, -- IC
}
} }
}
local selectionbox = { local selection_box = {
type = "fixed", type = "fixed",
fixed = { -8/16, -8/16, -8/16, 8/16, -5/16, 8/16 }, fixed = { -8/16, -8/16, -8/16, 8/16, -5/16, 8/16 },
} }
local digiline = { local digiline = {
receptor = {}, receptor = {},
effector = { effector = {
action = function (pos, node, channel, msg) action = function(pos, node, channel, msg)
lc_update (pos, {type = "digiline", channel = channel, msg = msg}) run(pos, {type = "digiline", channel = channel, msg = msg})
end end
} }
} }
local function on_receive_fields(pos, form_name, fields)
if not fields.program then
return
end
reset(pos)
reset_meta(pos, fields.code)
local err = run(pos, {type="program"})
if err then
print(err)
reset_meta(pos, fields.code, err)
end
end
for a = 0, 1 do -- 0 = off; 1 = on for a = 0, 1 do -- 0 = off 1 = on
for b = 0, 1 do for b = 0, 1 do
for c = 0, 1 do for c = 0, 1 do
for d = 0, 1 do for d = 0, 1 do
local cid = tostring(d)..tostring(c)..tostring(b)..tostring(a)
local node_name = BASENAME..cid
local top = "jeija_luacontroller_top.png"
if a == 1 then
top = top.."^jeija_luacontroller_LED_A.png"
end
if b == 1 then
top = top.."^jeija_luacontroller_LED_B.png"
end
if c == 1 then
top = top.."^jeija_luacontroller_LED_C.png"
end
if d == 1 then
top = top.."^jeija_luacontroller_LED_D.png"
end
local cid = tostring(d)..tostring(c)..tostring(b)..tostring(a) local groups
local nodename = BASENAME..cid if a + b + c + d ~= 0 then
local top = "jeija_luacontroller_top.png" groups = {dig_immediate=2, not_in_creative_inventory=1, overheat = 1}
if a == 1 then else
top = top.."^jeija_luacontroller_LED_A.png" groups = {dig_immediate=2, overheat = 1}
end end
if b == 1 then
top = top.."^jeija_luacontroller_LED_B.png"
end
if c == 1 then
top = top.."^jeija_luacontroller_LED_C.png"
end
if d == 1 then
top = top.."^jeija_luacontroller_LED_D.png"
end
if a + b + c + d ~= 0 then output_rules[cid] = {}
groups = {dig_immediate=2, not_in_creative_inventory=1, overheat = 1} input_rules[cid] = {}
else if a == 1 then table.insert(output_rules[cid], rules.a) end
groups = {dig_immediate=2, overheat = 1} if b == 1 then table.insert(output_rules[cid], rules.b) end
end if c == 1 then table.insert(output_rules[cid], rules.c) end
if d == 1 then table.insert(output_rules[cid], rules.d) end
output_rules[cid] = {} if a == 0 then table.insert( input_rules[cid], rules.a) end
input_rules[cid] = {} if b == 0 then table.insert( input_rules[cid], rules.b) end
if (a == 1) then table.insert(output_rules[cid], rules.a) end if c == 0 then table.insert( input_rules[cid], rules.c) end
if (b == 1) then table.insert(output_rules[cid], rules.b) end if d == 0 then table.insert( input_rules[cid], rules.d) end
if (c == 1) then table.insert(output_rules[cid], rules.c) end
if (d == 1) then table.insert(output_rules[cid], rules.d) end
if (a == 0) then table.insert(input_rules[cid], rules.a) end local mesecons = {
if (b == 0) then table.insert(input_rules[cid], rules.b) end effector = {
if (c == 0) then table.insert(input_rules[cid], rules.c) end rules = input_rules[cid],
if (d == 0) then table.insert(input_rules[cid], rules.d) end action_change = function (pos, _, rule_name, new_state)
update_real_port_states(pos, rule_name, new_state)
local mesecons = { run(pos, {type=new_state, pin=rule_name})
effector = end,
{
rules = input_rules[cid],
action_change = function (pos, _, rulename, newstate)
lc_update_real_portstates(pos, rulename, newstate)
lc_update(pos, {type=newstate, pin=rulename})
end,
},
receptor =
{
state = mesecon.state.on,
rules = output_rules[cid]
}
}
minetest.register_node(nodename, {
description = "Luacontroller",
drawtype = "nodebox",
tiles = {
top,
"jeija_microcontroller_bottom.png",
"jeija_microcontroller_sides.png",
"jeija_microcontroller_sides.png",
"jeija_microcontroller_sides.png",
"jeija_microcontroller_sides.png"
}, },
receptor = {
inventory_image = top, state = mesecon.state.on,
paramtype = "light", rules = output_rules[cid]
groups = groups, }
drop = BASENAME.."0000",
sunlight_propagates = true,
selection_box = selectionbox,
node_box = nodebox,
on_construct = reset_meta,
on_receive_fields = function(pos, formname, fields)
if not fields.program then
return
end
reset(pos)
reset_meta(pos, fields.code)
local err = lc_update(pos, {type="program"})
if err then
print(err)
reset_meta(pos, fields.code, err)
end
end,
on_timer = handle_timer,
sounds = default.node_sound_stone_defaults(),
mesecons = mesecons,
digiline = digiline,
virtual_portstates = { a = a == 1, -- virtual portstates are
b = b == 1, -- the ports the the
c = c == 1, -- controller powers itself
d = d == 1},-- so those that light up
after_dig_node = function (pos, node)
mesecon:receptor_off(pos, output_rules)
end,
is_luacontroller = true,
})
end
end
end
end
------------------------------
-- overheated luacontroller --
------------------------------
local mesecons_burnt = {
effector =
{
rules = mesecon.rules.flat,
action_change = function (pos, _, rulename, newstate)
-- only update portstates when changes are triggered
lc_update_real_portstates(pos, rulename, newstate)
end
} }
}
minetest.register_node(node_name, {
description = "LuaController",
drawtype = "nodebox",
tiles = {
top,
"jeija_microcontroller_bottom.png",
"jeija_microcontroller_sides.png",
"jeija_microcontroller_sides.png",
"jeija_microcontroller_sides.png",
"jeija_microcontroller_sides.png"
},
inventory_image = top,
paramtype = "light",
groups = groups,
drop = BASENAME.."0000",
sunlight_propagates = true,
selection_box = selection_box,
node_box = node_box,
on_construct = reset_meta,
on_receive_fields = on_receive_fields,
on_timer = handle_timer,
sounds = default.node_sound_stone_defaults(),
mesecons = mesecons,
digiline = digiline,
-- Virtual portstates are the ports that
-- the node shows as powered up (light up).
virtual_portstates = {
a = a == 1,
b = b == 1,
c = c == 1,
d = d == 1,
},
after_dig_node = function (pos, node)
mesecon:receptor_off(pos, output_rules)
end,
is_luacontroller = true,
})
end
end
end
end
------------------------------
-- Overheated LuaController --
------------------------------
minetest.register_node(BASENAME .. "_burnt", { minetest.register_node(BASENAME .. "_burnt", {
drawtype = "nodebox", drawtype = "nodebox",
@ -528,21 +601,17 @@ minetest.register_node(BASENAME .. "_burnt", {
selection_box = selectionbox, selection_box = selectionbox,
node_box = nodebox, node_box = nodebox,
on_construct = reset_meta, on_construct = reset_meta,
on_receive_fields = function(pos, formname, fields) on_receive_fields = on_receive_fields,
if fields.quit then
return
end
reset(pos)
reset_meta(pos, fields.code)
local err = lc_update(pos, {type="program"})
if err then
print(err)
reset_meta(pos, fields.code, err)
end
end,
sounds = default.node_sound_stone_defaults(), sounds = default.node_sound_stone_defaults(),
virtual_portstates = {a = false, b = false, c = false, d = false}, virtual_portstates = {a = false, b = false, c = false, d = false},
mesecons = mesecons_burnt, mesecons = {
effector = {
rules = mesecon.rules.flat,
action_change = function(pos, _, rule_name, new_state)
update_real_portstates(pos, rule_name, new_state)
end,
},
},
}) })
------------------------ ------------------------

View File

@ -638,7 +638,7 @@ function yc_update_real_portstates(pos, node, rulename, newstate)
end end
local n = meta:get_int("real_portstates") - 1 local n = meta:get_int("real_portstates") - 1
if n < 0 then if n < 0 then
legacy_update_ports(pos) mesecon.legacy_update_ports(pos)
n = meta:get_int("real_portstates") - 1 n = meta:get_int("real_portstates") - 1
end end
local L = {} local L = {}
@ -663,7 +663,7 @@ function yc_get_real_portstates(pos) -- determine if ports are powered (by itsel
local L = {} local L = {}
local n = meta:get_int("real_portstates") - 1 local n = meta:get_int("real_portstates") - 1
if n < 0 then if n < 0 then
return legacy_update_ports(pos) return mesecon.legacy_update_ports(pos)
end end
for _, index in ipairs({"a", "b", "c", "d"}) do for _, index in ipairs({"a", "b", "c", "d"}) do
L[index] = ((n%2) == 1) L[index] = ((n%2) == 1)