Turtle subroutine working!

Code is somewhat more organized.

Fixing problem of it dropping then picking up item for no reason...
master
zaners123 2021-05-08 16:02:05 -07:00
parent 8bdfcdfd3f
commit b7e5dc958c
6 changed files with 35 additions and 250 deletions

View File

@ -1,192 +0,0 @@
function getSandbox()
local sandbox = {
_VERSION = "sandbox 0.5",
_DESCRIPTION = "A pure-lua solution for running untrusted Lua code.",
_URL = "https://github.com/kikito/sandbox.lua",
_LICENSE = [[
MIT LICENSE
Copyright (c) 2021 Enrique García Cota
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
]],
}
-- quotas don't work in LuaJIT since debug.sethook works differently there
local quota_supported = type(_G.jit) == "nil"
sandbox.quota_supported = quota_supported
-- PUC-Rio Lua 5.1 does not support deactivation of bytecode
--local bytecode_blocked = _ENV or type(_G.jit) == "table"
local bytecode_blocked = false
sandbox.bytecode_blocked = bytecode_blocked
-- The base environment is merged with the given env option (or an empty table, if no env provided)
--
local BASE_ENV = {}
-- List of unsafe packages/functions:
--
-- * string.rep: can be used to allocate millions of bytes in 1 operation
-- * {set|get}metatable: can be used to modify the metatable of global objects (strings, integers)
-- * collectgarbage: can affect performance of other systems
-- * dofile: can access the server filesystem
-- * _G: It has access to everything. It can be mocked to other things though.
-- * load{file|string}: All unsafe because they can grant acces to global env
-- * raw{get|set|equal}: Potentially unsafe
-- * module|require|module: Can modify the host settings
-- * string.dump: Can display confidential server info (implementation of functions)
-- * math.randomseed: Can affect the host sytem
-- * io.*, os.*: Most stuff there is unsafe, see below for exceptions
-- Safe packages/functions below
([[
_VERSION assert error ipairs next pairs
pcall select tonumber tostring type unpack xpcall
coroutine.create coroutine.resume coroutine.running coroutine.status
coroutine.wrap coroutine.yield
math.abs math.acos math.asin math.atan math.atan2 math.ceil
math.cos math.cosh math.deg math.exp math.fmod math.floor
math.frexp math.huge math.ldexp math.log math.log10 math.max
math.min math.modf math.pi math.pow math.rad math.random
math.sin math.sinh math.sqrt math.tan math.tanh
os.clock os.difftime os.time
string.byte string.char string.find string.format string.gmatch
string.gsub string.len string.lower string.match string.reverse
string.sub string.upper
table.insert table.maxn table.remove table.sort
]]):gsub('%S+', function(id)
local module, method = id:match('([^%.]+)%.([^%.]+)')
if module then
BASE_ENV[module] = BASE_ENV[module] or {}
BASE_ENV[module][method] = _G[module][method]
else
BASE_ENV[id] = _G[id]
end
end)
local function protect_module(module, module_name)
return setmetatable({}, {
__index = module,
__newindex = function(_, attr_name, _)
error('Can not modify ' .. module_name .. '.' .. attr_name .. '. Protected by the sandbox.')
end
})
end
('coroutine math os string table'):gsub('%S+', function(module_name)
BASE_ENV[module_name] = protect_module(BASE_ENV[module_name], module_name)
end)
-- auxiliary functions/variables
local string_rep = string.rep
local function sethook(f, key, quota)
if type(debug) ~= 'table' or type(debug.sethook) ~= 'function' then return end
debug.sethook(f, key, quota)
end
local function cleanup()
sethook()
string.rep = string_rep -- luacheck: no global
end
-- Public interface: sandbox.protect
function sandbox.protect(code, options)
options = options or {}
local quota = false
if options.quota and not quota_supported then
error("options.quota is not supported on this environment (usually LuaJIT). Please unset options.quota")
end
if options.quota ~= false then
quota = options.quota or 500000
end
assert(type(code) == 'string', "expected a string")
local passed_env = options.env or {}
local env = {}
for k, v in pairs(BASE_ENV) do
local pv = passed_env[k]
if pv ~= nil then
env[k] = pv
else
env[k] = v
end
end
setmetatable(env, { __index = options.env })
env._G = env
local f
if bytecode_blocked then
f = assert(load(code, nil, 't', env))
else
f = (loadstring(code))
if (not f) then
return (function() return nil end)
end
setfenv(f, env)
end
return function(...)
if quota and quota_supported then
local timeout = function()
cleanup()
error('Quota exceeded: ' .. tostring(quota))
end
sethook(timeout, "", quota)
end
string.rep = nil -- luacheck: no global
local t = table.pack(pcall(f, ...))
cleanup()
if not t[1] then error(t[2]) end
return table.unpack(t, 2, t.n)
end
end
-- Public interface: sandbox.run
function sandbox.run(code, options, ...)
return sandbox.protect(code, options)(...)
end
-- make sandbox(f) == sandbox.protect(f)
setmetatable(sandbox, {__call = function(_,code,o) return sandbox.protect(code,o) end})
return sandbox
end

View File

@ -1,15 +0,0 @@
--- Functions in this file are callable by user-code
function stuff()
return "Success! I am result of stuff()"
end
--function moveForward (turtle) turtle:turtle_move_withHeading( 1, 0, 0) end
--function moveBackward (turtle) turtle:turtle_move_withHeading(-1, 0, 0) end
--function moveRight (turtle) turtle:turtle_move_withHeading( 0, 1, 0) end
--function moveLeft (turtle) turtle:turtle_move_withHeading( 0,-1, 0) end
--function moveUp (turtle) turtle:turtle_move_withHeading( 0, 0, 1) end
--function moveDown (turtle) turtle:turtle_move_withHeading( 0, 0,-1) end
--
--function turnRight (turtle) turtle:set_heading(turtle:get_heading()-1) end
--function turnLeft (turtle) turtle:set_heading(turtle:get_heading()+1) end

View File

@ -1,3 +1,6 @@
--[[
The turtle block is only used to spawn the turtle entity, then it deletes itself
]]
minetest.register_node("computertest:turtle", {
tiles = {
"computertest_top.png",

View File

@ -11,19 +11,15 @@ local function sandbox(codeString)
end
local function getTurtle(id) return computertest.turtles[id] end
local function runTurtleCommand(turtle, command)
if command==nil or command=="" then return nil end
--TODO eventually replace this with some kinda lua sandbox
command = "function init(turtle) return "..command.." end"
minetest.log("COMMAND IS \""..command.."\"")
sandbox(command)()
local res = init(turtle)
if (res==nil) then
return "Returned nil"
elseif (type(res)=="string") then
return res
local function upload_code_to_turtle(turtle, code_string,run_for_result)
turtle.codeUncompiled = code_string
turtle.code = sandbox(turtle.codeUncompiled)
if (run_for_result) then
--TODO run subroutine once, if it returns a value, return that here
return "Ran"
end
return "Done. Didn't return string."
return turtle.code ~= nil
end
local function get_formspec_inventory(self)
return "size[12,5;]"
@ -58,7 +54,6 @@ local function get_formspec_upload(turtle)
.."set_focus[upload;true]"
;
end
minetest.register_on_player_receive_fields(function(player, formname, fields)
local function isForm(name)
return string.sub(formname,1,string.len(name))==name
@ -77,7 +72,10 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
local id = tonumber(string.sub(formname,1+string.len(FORMNAME_TURTLE_TERMINAL)))
local turtle = getTurtle(id)
turtle.lastCommandRan = fields.terminal_in
local commandResult = runTurtleCommand(turtle, fields.terminal_in)
local command = fields.terminal_in
if command==nil or command=="" then return nil end
command = "function init(turtle) return "..command.." end"
local commandResult = upload_code_to_turtle(turtle, command, true)
if (commandResult==nil) then
minetest.close_formspec(player:get_player_name(),FORMNAME_TURTLE_TERMINAL..id)
return true
@ -89,11 +87,7 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
local id = tonumber(string.sub(formname,1+string.len(FORMNAME_TURTLE_UPLOAD)))
if (fields.button_upload == nil or fields.upload == nil) then return true end
local turtle = getTurtle(id)
--minetest.debug("CODE UPLOADED, NEAT",dump(id),dump(formname),dump(fields),dump(turtle))
turtle.codeUncompiled = fields.upload
turtle.code = sandbox(turtle.codeUncompiled)
if turtle.code==nil then return true end--Given malformed code
-- This turtle.code is used later
return not upload_code_to_turtle(turtle, fields.upload,false)
else
return false--Unknown formname, input not processed
end
@ -163,9 +157,6 @@ minetest.register_entity("computertest:turtle", {
---@returns true on success
---
turtle_move_withHeading = function (turtle,numForward,numRight,numUp)
--minetest.log("YAW"..dump(turtle.object:get_yaw()))
--minetest.log("NUMFORWARD"..dump(numForward))
--minetest.log("NUMRIGHT"..dump(numRight))
local new_pos = turtle:getNearbyPos(numForward,numRight,numUp)
--Verify new pos is empty
if (minetest.get_node(new_pos).name~="air") then
@ -189,8 +180,9 @@ minetest.register_entity("computertest:turtle", {
end,
mine = function(turtle, nodeLocation)
local node = minetest.get_node(nodeLocation)
if (node.name=="air") then return false end
local drops = minetest.get_node_drops(node)
minetest.remove_node(nodeLocation)
for _, itemname in ipairs(drops) do
local stack = ItemStack(itemname)
--TODO This doesn't actually need to drop-then-undrop the item for no reason
@ -208,16 +200,16 @@ minetest.register_entity("computertest:turtle", {
end
end
end
minetest.remove_node(nodeLocation)
turtle:yield("Mining")
return true
end,
-- MAIN TURTLE INTERFACE ---------------------------------------
-- TODO put turtle interface into a stack, so the turtle can't immediately mine hundreds of blocks (only one mine per second and move two blocks per second or something)
-- Wouldn't work since the player couldn't use stateful functions such as getting the fuel level
-- TODO The TurtleEntity thread would need to pause itself after calling any of these functions. This pause would then return state back to the
-- TODO move this to a literal OO interface wrapper thingy
interface = {
yield,
moveForward, moveBackward, moveRight, moveLeft, moveUp, moveDown,
turnLeft, turnRight,
mineForward, mineUp, mineDown,
},
yield = function(turtle,reason) if (coroutine.running() == turtle.coroutine) then coroutine.yield(reason) end end,
moveForward = function(turtle) turtle:turtle_move_withHeading( 1, 0, 0) end,
moveBackward = function(turtle) turtle:turtle_move_withHeading(-1, 0, 0) end,
@ -237,7 +229,7 @@ local timer = 0
minetest.register_globalstep(function(dtime)
timer = timer + dtime
if (timer >= computertest.config.globalstep_interval) then
for id,turtle in pairs(computertest.turtles) do
for _,turtle in pairs(computertest.turtles) do
if turtle.coroutine then
if coroutine.status(turtle.coroutine)=="dead" then
--minetest.log("turtle #"..id.." has coroutine, but it's already done running")

7
examples/quarry.lua Normal file
View File

@ -0,0 +1,7 @@
function init(turtle)
local x = 0;
while true do
turtle:mineForward()
turtle:moveForward()
end
end

View File

@ -6,15 +6,5 @@ computertest = {
num_turtles = 0,
}
local modpath = minetest.get_modpath("computertest")
dofile(modpath.."/Block/Turtle.lua")
dofile(modpath.."/TurtleEntity.lua")
dofile(modpath.."/TurtleInterface.lua")
dofile(modpath.."/Sandbox.lua")
dofile(modpath.."/block/turtle.lua")
dofile(modpath.."/entity/turtle.lua")