Add luacontroller, a microcontroller that you can code in lua.

It still misses some functionality such as a persistent memory and a
timer, but that is subject to change. The code runs in a sandbox.
Speaking long term this will hopefully replace the old controller.
This commit is contained in:
Jeija 2013-01-13 00:18:25 +01:00
parent 841bc70b97
commit bd749ec4d4
8 changed files with 304 additions and 0 deletions

View File

@ -0,0 +1 @@
mesecons

View File

@ -0,0 +1,303 @@
-- Reference
-- ports = get_real_portstates(pos): gets if inputs are powered from outside
-- newport = merge_portstates(state1, state2): just does result = state1 or state2 for every port
-- action_setports(pos, ports, vports): activates/deactivates the mesecons according to the portstates (helper for action)
-- action(pos, ports): Applies new portstates to a luacontroller at pos
-- update(pos): updates the controller at pos by executing the code
-- 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
--
-- 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 = {}
rules.a = {x = -1, y = 0, z = 0}
rules.b = {x = 0, y = 0, z = 1}
rules.c = {x = 1, y = 0, z = 0}
rules.d = {x = 0, y = 0, z = -1}
local get_real_portstates = function(pos) -- determine if ports are powered (by itself or from outside)
ports = {
a = mesecon:is_power_on(mesecon:addPosRule(pos, rules.a))
and mesecon:rules_link(mesecon:addPosRule(pos, rules.a), pos),
b = mesecon:is_power_on(mesecon:addPosRule(pos, rules.b))
and mesecon:rules_link(mesecon:addPosRule(pos, rules.b), pos),
c = mesecon:is_power_on(mesecon:addPosRule(pos, rules.c))
and mesecon:rules_link(mesecon:addPosRule(pos, rules.c), pos),
d = mesecon:is_power_on(mesecon:addPosRule(pos, rules.d))
and mesecon:rules_link(mesecon:addPosRule(pos, rules.d), pos),
}
return ports
end
local merge_portstates = function (ports, vports)
local npo = {a=false, b=false, c=false, d=false}
npo.a = vports.a or ports.a
npo.b = vports.b or ports.b
npo.c = vports.c or ports.c
npo.d = vports.d or ports.d
return npo
end
local action_setports = function (pos, ports, vports)
if vports.a ~= ports.a then
if ports.a then mesecon:receptor_on(pos, {rules.a})
else mesecon:receptor_off(pos, {rules.a}) end
end
if vports.b ~= ports.b then
if ports.b then mesecon:receptor_on(pos, {rules.b})
else mesecon:receptor_off(pos, {rules.b}) end
end
if vports.c ~= ports.c then
if ports.c then mesecon:receptor_on(pos, {rules.c})
else mesecon:receptor_off(pos, {rules.c}) end
end
if vports.d ~= ports.d then
if ports.d then mesecon:receptor_on(pos, {rules.d})
else mesecon:receptor_off(pos, {rules.d}) end
end
end
local action = function (pos, ports)
local vports = minetest.registered_nodes[minetest.env:get_node(pos).name].virtual_portstates;
local name = BASENAME
..tonumber(ports.d and 1 or 0)
..tonumber(ports.c and 1 or 0)
..tonumber(ports.b and 1 or 0)
..tonumber(ports.a and 1 or 0)
mesecon:swap_node(pos, name)
action_setports (pos, ports, vports)
end
-- Overheat Stuff
local heat = function (meta) -- warm up
h = meta:get_int("heat")
if h ~= nil then
meta:set_int("heat", h + 1)
end
end
local cool = function (meta) -- cool down after a while
h = meta:get_int("heat")
if h ~= nil then
meta:set_int("heat", h - 1)
end
end
local overheat = function (meta) -- determine if too hot
h = meta:get_int("heat")
if h == nil then return true end -- if nil then overheat
if h > 30 then
return true
else
return false
end
end
local overheat_off = function(pos)
rules = mesecon:get_rules("mesecons_microcontroller:microcontroller1111")
mesecon:receptor_off(pos, rules)
end
local update = function (pos)
local meta = minetest.env:get_meta(pos)
code = meta:get_string("code")
-- Clean code
local prohibited = {"while", "for", "repeat", "until"}
for _, p in ipairs(prohibited) do
if string.find(code, p) then
return "Prohibited command: "..p
end
end
-- Gather variables for the environment
local vports = minetest.registered_nodes[minetest.env:get_node(pos).name].virtual_portstates
vports = {a = vports.a, b = vports.b, c = vports.c, d = vports.d}
local rports = get_real_portstates(pos)
local env = { print = print,
selfpos = pos,
dump = dump,
pin = merge_portstates(vports, rports),
port = vports}
-- Create Sandbox
if code:byte(1) == 27 then
return "You Hacker You! Don't use binary code!"
end
local f, msg = loadstring(code)
if not f then return msg end
setfenv(f, env)
success, msg = pcall(f)
-- Overheat protection
heat(meta)
minetest.after(0.5, cool, meta)
if overheat(meta) then
minetest.env:remove_node(pos)
minetest.after(0.2, overheat_off, pos) -- wait for pending operations
minetest.env:add_item(pos, BASENAME.."0000")
return
end
-- Actually set the ports
if not success then
return msg
else
action(pos, env.port)
end
end
local reset_meta = function(pos, code, errmsg)
local meta = minetest.env:get_meta(pos)
code = code or "";
errmsg = errmsg or "";
errmsg = string.gsub(errmsg, "%[", "(") -- would otherwise
errmsg = string.gsub(errmsg, "%]", ")") -- corrupt formspec
meta:set_string("code", code)
meta:set_string("formspec", "size[10,8]"..
"textarea[0.2,0.4;10.2,5;code;Code:;"..code.."]"..
"button[3.5,7.5;2,0;program;Program]"..
"image_button_exit[9.62,-0.35;0.7,0.7;jeija_close_window.png;exit;]"..
"label[0.1,4.5;"..errmsg.."]")
meta:set_int("heat", 0)
end
local reset = function (pos)
action(pos, {a=false, b=false, c=false, d=false})
end
-- ______
-- |
-- | | |
-- |___| | __ ___ _ __ _ _
-- | | | | |\ | | |_| | | | | |_ |_|
-- | |______ |__| | \| | | \ |__| |_ |_ |_ |\
--
-- Actually register all the stuff:
for a = 0, 1 do
for b = 0, 1 do
for c = 0, 1 do
for d = 0, 1 do
local nodename = BASENAME..tostring(d)..tostring(c)..tostring(b)..tostring(a)
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
if a + b + c + d ~= 0 then
groups = {dig_immediate=2, not_in_creative_inventory=1}
else
groups = {dig_immediate=2}
end
local output_rules={}
if (a == 1) then table.insert(output_rules, rules.a) end
if (b == 1) then table.insert(output_rules, rules.b) end
if (c == 1) then table.insert(output_rules, rules.c) end
if (d == 1) then table.insert(output_rules, rules.d) end
local input_rules={}
if (a == 0) then table.insert(input_rules, rules.a) end
if (b == 0) then table.insert(input_rules, rules.b) end
if (c == 0) then table.insert(input_rules, rules.c) end
if (d == 0) then table.insert(input_rules, rules.d) end
local mesecons = {effector =
{
rules = input_rules,
action_change = function (pos)
update(pos)
end
}}
if nodename ~= BASENAME.."0000" then
mesecons.receptor = {
state = mesecon.state.on,
rules = output_rules
}
end
local nodebox = {
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 selectionbox = {
type = "fixed",
fixed = { -8/16, -8/16, -8/16, 8/16, -5/16, 8/16 },
}
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"
},
paramtype = "light",
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)
reset(pos)
reset_meta(pos, fields.code)
local err = update(pos)
if err then print(err) end
reset_meta(pos, fields.code, err)
end,
mesecons = mesecons,
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,
})
end
end
end
end
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', ''},
}
})

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB