mirror of
https://github.com/minetest-mods/mesecons.git
synced 2024-12-27 02:40:18 +01:00
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:
parent
d325292291
commit
bd1766e448
@ -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
|
||||||
|
|
||||||
|
@ -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,
|
||||||
|
},
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
------------------------
|
------------------------
|
||||||
|
@ -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)
|
||||||
|
Loading…
Reference in New Issue
Block a user