diff options
Diffstat (limited to 'mesecons/mesecons_luacontroller/init.lua')
-rw-r--r-- | mesecons/mesecons_luacontroller/init.lua | 642 |
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', ''}, - } -}) - |