summaryrefslogtreecommitdiff
path: root/mesecons/mesecons_luacontroller/init.lua
diff options
context:
space:
mode:
Diffstat (limited to 'mesecons/mesecons_luacontroller/init.lua')
-rw-r--r--mesecons/mesecons_luacontroller/init.lua642
1 files changed, 0 insertions, 642 deletions
diff --git a/mesecons/mesecons_luacontroller/init.lua b/mesecons/mesecons_luacontroller/init.lua
deleted file mode 100644
index 839d150..0000000
--- a/mesecons/mesecons_luacontroller/init.lua
+++ /dev/null
@@ -1,642 +0,0 @@
--- ______
--- |
--- |
--- | __ ___ _ __ _ _
--- | | | | | |\ | | |_| | | | | |_ |_|
--- |___| |______ |__| | \| | | \ |__| |_ |_ |_ |\
--- |
--- |
---
-
--- Reference
--- ports = get_real_port_states(pos): gets if inputs are powered from outside
--- newport = merge_port_states(state1, state2): just does result = state1 or state2 for every port
--- set_port(pos, rule, state): activates/deactivates the mesecons according to the port states
--- set_port_states(pos, ports): Applies new port states to a LuaController at pos
--- 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
--- resetn(pos): performs a hardware reset, turns off all ports
---
--- The Sandbox
--- The whole code of the controller runs in a sandbox,
--- a very restricted environment.
--- However, as this does not prevent you from using e.g. loops,
--- we need to check for these prohibited commands first.
--- Actually the only way to damage the server is to
--- use too much memory from the sandbox.
--- You can add more functions to the environment
--- (see where local env is defined)
--- Something nice to play is is appending minetest.env to it.
-
-local BASENAME = "mesecons_luacontroller:luacontroller"
-
-local rules = {
- a = {x = -1, y = 0, z = 0, name="A"},
- b = {x = 0, y = 0, z = 1, name="B"},
- c = {x = 1, y = 0, z = 0, name="C"},
- d = {x = 0, y = 0, z = -1, name="D"},
-}
-
-
-------------------
--- Action stuff --
-------------------
--- These helpers are required to set the port states of the luacontroller
-
-local function update_real_port_states(pos, rule_name, new_state)
- local meta = minetest.get_meta(pos)
- if rule_name == nil then
- meta:set_int("real_portstates", 1)
- return
- end
- local n = meta:get_int("real_portstates") - 1
- local L = {}
- for i = 1, 4 do
- L[i] = n % 2
- n = math.floor(n / 2)
- end
- -- (0,-1) (-1,0) (1,0) (0,1)
- local pos_to_side = { 4, 1, nil, 3, 2 }
- 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
- end
- else
- local port = pos_to_side[rule_name.x + (2 * rule_name.z) + 3]
- L[port] = (new_state == "on") and 1 or 0
- end
- meta:set_int("real_portstates",
- 1 +
- 1 * L[1] +
- 2 * L[2] +
- 4 * L[3] +
- 8 * L[4])
-end
-
-
-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 L = {}
- local n = meta:get_int("real_portstates") - 1
- for _, name in ipairs(port_names) do
- L[name] = ((n % 2) == 1)
- n = math.floor(n / 2)
- end
- return L
-end
-
-
-local function merge_port_states(ports, vports)
- return {
- a = ports.a or vports.a,
- b = ports.b or vports.b,
- c = ports.c or vports.c,
- d = ports.d or vports.d,
- }
-end
-
-local function generate_name(ports)
- local d = ports.d and 1 or 0
- local c = ports.c and 1 or 0
- local b = ports.b and 1 or 0
- local a = ports.a and 1 or 0
- return BASENAME..d..c..b..a
-end
-
-
-local function set_port(pos, rule, state)
- if state then
- mesecon.receptor_on(pos, {rule})
- else
- mesecon.receptor_off(pos, {rule})
- end
-end
-
-
-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 name = node.name
- clean_port_states(ports)
- local vports = minetest.registered_nodes[name].virtual_portstates
- local new_name = generate_name(ports)
-
- if name ~= new_name and vports then
- -- Problem:
- -- We need to place the new node first so that when turning
- -- off some port, it won't stay on because the rules indicate
- -- there is an onstate output port there.
- -- When turning the output off then, it will however cause feedback
- -- so that the luacontroller will receive an "off" event by turning
- -- its output off.
- -- Solution / Workaround:
- -- Remember which output was turned off and ignore next "off" event.
- local meta = minetest.get_meta(pos)
- local ign = minetest.deserialize(meta:get_string("ignore_offevents")) or {}
- if ports.a and not vports.a and not mesecon.is_powered(pos, rules.a) then ign.A = true end
- if ports.b and not vports.b and not mesecon.is_powered(pos, rules.b) then ign.B = true end
- if ports.c and not vports.c and not mesecon.is_powered(pos, rules.c) then ign.C = true end
- if ports.d and not vports.d and not mesecon.is_powered(pos, rules.d) then ign.D = true end
- meta:set_string("ignore_offevents", minetest.serialize(ign))
-
- minetest.swap_node(pos, {name = new_name, 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.c ~= vports.c then set_port(pos, rules.c, ports.c) end
- if ports.d ~= vports.d then set_port(pos, rules.d, ports.d) end
- end
-end
-
-
------------------
--- Overheating --
------------------
-local function burn_controller(pos)
- local node = minetest.get_node(pos)
- node.name = BASENAME.."_burnt"
- minetest.swap_node(pos, node)
- minetest.get_meta(pos):set_string("lc_memory", "");
- -- Wait for pending operations
- minetest.after(0.2, mesecon.receptor_off, pos, mesecon.rules.flat)
-end
-
-local function overheat(pos, meta)
- if mesecon.do_overheat(pos) then -- If too hot
- burn_controller(pos)
- return true
- end
-end
-
-------------------------
--- Ignored off events --
-------------------------
-
-local function ignore_event(event, meta)
- if event.type ~= "off" then return false end
- local ignore_offevents = minetest.deserialize(meta:get_string("ignore_offevents")) or {}
- if ignore_offevents[event.pin.name] then
- ignore_offevents[event.pin.name] = nil
- meta:set_string("ignore_offevents", minetest.serialize(ignore_offevents))
- return true
- end
-end
-
--------------------------
--- Parsing and running --
--------------------------
-
-local function safe_print(param)
- print(dump(param))
-end
-
-local function safe_date()
- return(os.date("*t",os.time()))
-end
-
-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
- local luac_id = minetest.get_meta(pos):get_int("luac_id")
- mesecon.queue:add_action(pos, "lc_interrupt", {luac_id, iid}, time, iid, 1)
- end
- return interrupt
-end
-
-
-local function get_digiline_send(pos)
- if not digiline then return end
- return function(channel, msg)
- minetest.after(0, function()
- digiline:receptor_send(pos, digiline.rules.default, channel, msg)
- end)
- end
-end
-
-
-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
- local vports = minetest.registered_nodes[minetest.get_node(pos).name].virtual_portstates
- local vports_copy = {}
- for k, v in pairs(vports) do vports_copy[k] = v end
- local rports = get_real_port_states(pos)
-
- -- Create new library tables on each call to prevent one LuaController
- -- from breaking a library and messing up other LuaControllers.
- local env = {
- pin = merge_port_states(vports, rports),
- port = vports_copy,
- event = event,
- mem = mem,
- heat = minetest.get_meta(pos):get_int("heat"),
- heat_max = mesecon.setting("overheat_max", 20),
- print = safe_print,
- interrupt = get_interrupt(pos),
- digiline_send = get_digiline_send(pos),
- string = {
- byte = string.byte,
- char = string.char,
- format = string.format,
- gsub = string.gsub,
- len = string.len,
- lower = string.lower,
- upper = string.upper,
- rep = string.rep,
- reverse = string.reverse,
- sub = string.sub,
- },
- math = {
- abs = math.abs,
- acos = math.acos,
- asin = math.asin,
- atan = math.atan,
- atan2 = math.atan2,
- ceil = math.ceil,
- cos = math.cos,
- cosh = math.cosh,
- deg = math.deg,
- exp = math.exp,
- floor = math.floor,
- fmod = math.fmod,
- frexp = math.frexp,
- huge = math.huge,
- ldexp = math.ldexp,
- log = math.log,
- log10 = math.log10,
- max = math.max,
- min = math.min,
- modf = math.modf,
- pi = math.pi,
- pow = math.pow,
- rad = math.rad,
- random = math.random,
- sin = math.sin,
- sinh = math.sinh,
- sqrt = math.sqrt,
- tan = math.tan,
- tanh = math.tanh,
- },
- table = {
- concat = table.concat,
- insert = table.insert,
- maxn = table.maxn,
- remove = table.remove,
- sort = table.sort,
- },
- os = {
- clock = os.clock,
- difftime = os.difftime,
- time = os.time,
- datetable = safe_date,
- },
- }
- env._G = env
-
- for _, name in pairs(safe_globals) do
- env[name] = _G[name]
- end
-
- return env
-end
-
-
-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
- return nil, "Binary code prohibited."
- end
- local f, msg = loadstring(code)
- if not f then return nil, msg end
- setfenv(f, env)
-
- return function(...)
- debug.sethook(timeout, "", 10000)
- local ok, ret = pcall(f, ...)
- debug.sethook() -- Clear hook
- if not ok then error(ret) end
- return ret
- end
-end
-
-
-local function load_memory(meta)
- return minetest.deserialize(meta:get_string("lc_memory")) or {}
-end
-
-
-local function save_memory(pos, meta, mem)
- local memstring = minetest.serialize(remove_functions(mem))
- local memsize_max = mesecon.setting("luacontroller_memsize", 100000)
-
- if (#memstring <= memsize_max) then
- meta:set_string("lc_memory", memstring)
- else
- print("Error: Luacontroller memory overflow. "..memsize_max.." bytes available, "
- ..#memstring.." required. Controller overheats.")
- burn_controller(pos)
- end
-end
-
-
-local function run(pos, event)
- local meta = minetest.get_meta(pos)
- if overheat(pos) then return end
- if ignore_event(event, meta) then return end
-
- -- Load code & mem from meta
- local mem = load_memory(meta)
- local code = meta:get_string("code")
-
- local err = code_prohibited(code)
- if err then return err end
-
- -- Create environment
- local env = create_environment(pos, mem, event)
-
- -- Create the sandbox and execute code
- local f, msg = create_sandbox(code, env)
- if not f then return msg end
- local success, msg = pcall(f)
- if not success then return msg end
- if type(env.port) ~= "table" then
- return "Ports set are invalid."
- end
-
- -- Actually set the ports
- set_port_states(pos, env.port)
-
- -- Save memory. This may burn the luacontroller if a memory overflow occurs.
- save_memory(pos, meta, env.mem)
-end
-
-mesecon.queue:add_function("lc_interrupt", function (pos, luac_id, iid)
- -- There is no luacontroller anymore / it has been reprogrammed / replaced / burnt
- if (minetest.get_meta(pos):get_int("luac_id") ~= luac_id) then return end
- if (minetest.registered_nodes[minetest.get_node(pos).name].is_burnt) then return end
- run(pos, {type="interrupt", iid = iid})
-end)
-
-local function reset_meta(pos, code, errmsg)
- local meta = minetest.get_meta(pos)
- meta:set_string("code", code)
- code = minetest.formspec_escape(code or "")
- errmsg = minetest.formspec_escape(errmsg or "")
- meta:set_string("formspec", "size[10,8]"..
- "background[-0.2,-0.25;10.4,8.75;jeija_luac_background.png]"..
- "textarea[0.2,0.6;10.2,5;code;;"..code.."]"..
- "image_button[3.75,6;2.5,1;jeija_luac_runbutton.png;program;]"..
- "image_button_exit[9.72,-0.25;0.425,0.4;jeija_close_window.png;exit;]"..
- "label[0.1,5;"..errmsg.."]")
- meta:set_int("heat", 0)
- meta:set_int("luac_id", math.random(1, 65535))
-end
-
-local function reset(pos)
- set_port_states(pos, {a=false, b=false, c=false, d=false})
-end
-
-
------------------------
--- Node Registration --
------------------------
-
-local output_rules = {}
-local input_rules = {}
-
-local node_box = {
- type = "fixed",
- fixed = {
- {-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
- {-3/16, -6/16, -3/16, 3/16, -5/16, 3/16}, -- IC
- }
-}
-
-local selection_box = {
- type = "fixed",
- fixed = { -8/16, -8/16, -8/16, 8/16, -5/16, 8/16 },
-}
-
-local digiline = {
- receptor = {},
- effector = {
- action = function(pos, node, channel, msg)
- run(pos, {type = "digiline", channel = channel, msg = msg})
- 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 b = 0, 1 do
-for c = 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 groups
- if a + b + c + d ~= 0 then
- groups = {dig_immediate=2, not_in_creative_inventory=1, overheat = 1}
- else
- groups = {dig_immediate=2, overheat = 1}
- end
-
- output_rules[cid] = {}
- input_rules[cid] = {}
- if a == 1 then table.insert(output_rules[cid], rules.a) end
- if b == 1 then table.insert(output_rules[cid], rules.b) 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
- if b == 0 then table.insert( input_rules[cid], rules.b) end
- if c == 0 then table.insert( input_rules[cid], rules.c) end
- if d == 0 then table.insert( input_rules[cid], rules.d) end
-
- local mesecons = {
- effector = {
- rules = input_rules[cid],
- action_change = function (pos, _, rule_name, new_state)
- update_real_port_states(pos, rule_name, new_state)
- run(pos, {type=new_state, pin=rule_name})
- end,
- },
- receptor = {
- state = mesecon.state.on,
- rules = output_rules[cid]
- }
- }
-
- 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,
- 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", {
- drawtype = "nodebox",
- tiles = {
- "jeija_luacontroller_burnt_top.png",
- "jeija_microcontroller_bottom.png",
- "jeija_microcontroller_sides.png",
- "jeija_microcontroller_sides.png",
- "jeija_microcontroller_sides.png",
- "jeija_microcontroller_sides.png"
- },
- inventory_image = "jeija_luacontroller_burnt_top.png",
- is_burnt = true,
- paramtype = "light",
- groups = {dig_immediate=2, not_in_creative_inventory=1},
- drop = BASENAME.."0000",
- sunlight_propagates = true,
- selection_box = selection_box,
- node_box = node_box,
- on_construct = reset_meta,
- on_receive_fields = on_receive_fields,
- sounds = default.node_sound_stone_defaults(),
- virtual_portstates = {a = false, b = false, c = false, d = false},
- mesecons = {
- effector = {
- rules = mesecon.rules.flat,
- action_change = function(pos, _, rule_name, new_state)
- update_real_port_states(pos, rule_name, new_state)
- end,
- },
- },
-})
-
-------------------------
--- Craft Registration --
-------------------------
-
-minetest.register_craft({
- output = BASENAME.."0000 2",
- recipe = {
- {'mesecons_materials:silicon', 'mesecons_materials:silicon', 'group:mesecon_conductor_craftable'},
- {'mesecons_materials:silicon', 'mesecons_materials:silicon', 'group:mesecon_conductor_craftable'},
- {'group:mesecon_conductor_craftable', 'group:mesecon_conductor_craftable', ''},
- }
-})
-