diff --git a/luprex/cpp/core/animqueue.cpp b/luprex/cpp/core/animqueue.cpp index 15ecb585..55527692 100644 --- a/luprex/cpp/core/animqueue.cpp +++ b/luprex/cpp/core/animqueue.cpp @@ -55,19 +55,31 @@ static void parse_value(std::string_view vstr, AnimValue *v) { static AnimValue parse_anim_value(LuaCoreStack &LS, LuaSlot val, LuaSlot tmp) { AnimValue result; - if (LS.isboolean(val)) { - result.set_boolean(LS.ckboolean(val)); - } else if (LS.isnumber(val)) { - result.set_number(LS.cknumber(val)); - } else if (LS.isstring(val)) { - result.set_string(LS.ckstring(val)); - } else if (LS.isxyz(val)) { - result.set_dxyz(LS.ckxyz(val)); - } else if (LS.rawequal(val, LuaToken("auto"))) { - result.set_auto(); - } else { - result.set_uninitialized(); + auto tboolean = LS.tryboolean(val); + if (tboolean) { + result.set_boolean(*tboolean); + return result; } + auto tnumber = LS.trynumber(val); + if (tnumber) { + result.set_number(*tnumber); + return result; + } + auto tstring = LS.trystringview(val); + if (tstring) { + result.set_string(*tstring); + return result; + } + auto txyz = LS.tryxyz(val); + if (txyz) { + result.set_dxyz(*txyz); + return result; + } + if (LS.rawequal(val, LuaToken("auto"))) { + result.set_auto(); + return result; + } + result.set_uninitialized(); return result; } diff --git a/luprex/cpp/core/luastack.cpp b/luprex/cpp/core/luastack.cpp index b6a7e22b..f50a3338 100644 --- a/luprex/cpp/core/luastack.cpp +++ b/luprex/cpp/core/luastack.cpp @@ -96,95 +96,73 @@ void LuaCoreStack::argerr(const char *nm, const char *tp) const { luaL_error(L_, "'%s' should be %s", nm, tp); } -bool LuaCoreStack::isinteger(LuaSlot s) const { - if (lua_type(L_, s) == LUA_TNUMBER) { - lua_Number result = lua_tonumber(L_, s); - if (lua_Integer(result) == result) return true; +std::optional LuaCoreStack::tryboolean(LuaSlot s) const { + if (lua_type(L_, s) == LUA_TBOOLEAN) { + return lua_toboolean(L_, s); } - return false; + return std::nullopt; } -bool LuaCoreStack::isint(LuaSlot s) const { +std::optional LuaCoreStack::tryinteger(LuaSlot s) const { if (lua_type(L_, s) == LUA_TNUMBER) { lua_Number result = lua_tonumber(L_, s); - if (int(result) == result) return true; - } - return false; -} - -bool LuaCoreStack::isxyz(LuaSlot s) const { - if (lua_istable(L_, s) && (lua_nkeys(L_, s) == 3)) { - int top = lua_gettop(L_); - lua_rawgeti(L_, s, 3); - lua_rawgeti(L_, s, 2); - lua_rawgeti(L_, s, 1); - if (lua_isnumber(L_, -1) && lua_isnumber(L_, -2) && lua_isnumber(L_, -3)) { - lua_settop(L_, top); - return true; + if (lua_Integer(result) == result) { + return lua_Integer(result); } - lua_settop(L_, top); } - return false; + return std::nullopt; } -bool LuaCoreStack::ckboolean(LuaSlot s) const { - checkboolean(s, "value"); - return lua_toboolean(L_, s) ? true:false; -} - -lua_Integer LuaCoreStack::ckinteger(LuaSlot s) const { - checknumber(s, "value"); - luaL_checktype(L_, s, LUA_TNUMBER); - lua_Number result = lua_tonumber(L_, s); - lua_Integer iresult(result); - if (iresult != result) { - luaL_error(L_, "not a valid integer"); - return 0; +std::optional LuaCoreStack::tryint(LuaSlot s) const { + if (lua_type(L_, s) == LUA_TNUMBER) { + lua_Number result = lua_tonumber(L_, s); + if (int(result) == result) { + return int(result); + } } - return iresult; + return std::nullopt; } -int LuaCoreStack::ckint(LuaSlot s) const { - checknumber(s, "value"); - lua_Number result = lua_tonumber(L_, s); - int iresult(result); - if (iresult != result) { - luaL_error(L_, "not a valid int"); - return 0; +std::optional LuaCoreStack::trynumber(LuaSlot s) const { + if (lua_type(L_, s) == LUA_TNUMBER) { + return lua_tonumber(L_, s); } - return iresult; + return std::nullopt; } -lua_Number LuaCoreStack::cknumber(LuaSlot s) const { - checknumber(s, "value"); - return lua_tonumber(L_, s); +std::optional LuaCoreStack::trystring(LuaSlot s) const { + if (lua_type(L_, s) == LUA_TSTRING) { + size_t len; + const char *str = lua_tolstring(L_, s, &len); + return eng::string(str, len); + } + return std::nullopt; } -eng::string LuaCoreStack::ckstring(LuaSlot s) const { - checkstring(s, "value"); - size_t len; - const char *str = lua_tolstring(L_, s, &len); - return eng::string(str, len); +std::optional LuaCoreStack::trystringview(LuaSlot s) const { + if (lua_type(L_, s) == LUA_TSTRING) { + size_t len; + const char *str = lua_tolstring(L_, s, &len); + return std::string_view(str, len); + } + return std::nullopt; } -std::string_view LuaCoreStack::ckstringview(LuaSlot s) const { - checkstring(s, "value"); - size_t len; - const char *str = lua_tolstring(L_, s, &len); - return std::string_view(str, len); +std::optional LuaCoreStack::trythread(LuaSlot s) const { + if (lua_type(L_, s) == LUA_TTHREAD) { + return lua_tothread(L_, s); + } + return std::nullopt; } -lua_State *LuaCoreStack::ckthread(LuaSlot s) const { - checkthread(s, "value"); - return lua_tothread(L_, s); +std::optional LuaCoreStack::trytoken(LuaSlot s) const { + if (lua_type(L_, s) == LUA_TLIGHTUSERDATA) { + return LuaToken(lua_touserdata(L_, s)); + } + return std::nullopt; } -LuaToken LuaCoreStack::cktoken(LuaSlot s) const { - checktoken(s, "value"); - return LuaToken(lua_touserdata(L_, s)); -} - -util::DXYZ LuaCoreStack::ckxyz(LuaSlot s) const { +std::optional LuaCoreStack::tryxyz(LuaSlot s) const { if (lua_istable(L_, s) && (lua_nkeys(L_, s) == 3)) { int top = lua_gettop(L_); lua_rawgeti(L_, s, 3); @@ -200,10 +178,106 @@ util::DXYZ LuaCoreStack::ckxyz(LuaSlot s) const { } lua_settop(L_, top); } - argerr("argument", "vector"); - return util::DXYZ(); + return std::nullopt; } +bool LuaCoreStack::trytable(LuaSlot s) const { + return lua_istable(L_, s); +} + +bool LuaCoreStack::trynil(LuaSlot s) const { + return lua_isnil(L_, s); +} + +bool LuaCoreStack::tryfunction(LuaSlot s) const { + return lua_isfunction(L_, s); +} + +bool LuaCoreStack::trycfunction(LuaSlot s) const { + return lua_iscfunction(L_, s); +} + +bool LuaCoreStack::trytangible(LuaSlot s) const { + return (lua_istable(L_, s) && gettabletype(s) == LUA_TT_TANGIBLE); +} + + + +bool LuaCoreStack::ckboolean(LuaSlot s, const char *argname) const { + auto result = tryboolean(s); + if (!result) argerr(argname, "boolean"); + return *result; +} + +lua_Integer LuaCoreStack::ckinteger(LuaSlot s, const char *argname) const { + auto result = tryinteger(s); + if (!result) argerr(argname, "integer"); + return *result; +} + +int LuaCoreStack::ckint(LuaSlot s, const char *argname) const { + auto result = tryint(s); + if (!result) argerr(argname, "int"); + return *result; +} + +lua_Number LuaCoreStack::cknumber(LuaSlot s, const char *argname) const { + auto result = trynumber(s); + if (!result) argerr(argname, "number"); + return *result; +} + +eng::string LuaCoreStack::ckstring(LuaSlot s, const char *argname) const { + auto result = trystring(s); + if (!result) argerr(argname, "string"); + return *result; +} + +std::string_view LuaCoreStack::ckstringview(LuaSlot s, const char *argname) const { + auto result = trystringview(s); + if (!result) argerr(argname, "string"); + return *result; +} + +lua_State * LuaCoreStack::ckthread(LuaSlot s, const char *argname) const { + auto result = trythread(s); + if (!result) argerr(argname, "thread"); + return *result; +} + +LuaToken LuaCoreStack::cktoken(LuaSlot s, const char *argname) const { + auto result = trytoken(s); + if (!result) argerr(argname, "token"); + return *result; +} + +util::DXYZ LuaCoreStack::ckxyz(LuaSlot s, const char *argname) const { + auto result = tryxyz(s); + if (!result) argerr(argname, "xyz"); + return *result; +} + +void LuaCoreStack::cktable(LuaSlot s, const char *argname) const { + if (!trytable(s)) argerr(argname, "table"); +} + +void LuaCoreStack::cknil(LuaSlot s, const char *argname) const { + if (!trynil(s)) argerr(argname, "nil"); +} + +void LuaCoreStack::ckfunction(LuaSlot s, const char *argname) const { + if (!tryfunction(s)) argerr(argname, "function"); +} + +void LuaCoreStack::ckcfunction(LuaSlot s, const char *argname) const { + if (!trycfunction(s)) argerr(argname, "c-function"); +} + +void LuaCoreStack::cktangible(LuaSlot s, const char *argname) const { + if (!trytangible(s)) argerr(argname, "tangible"); +} + + void LuaCoreStack::clearmetatable(LuaSlot tab) const { lua_pushnil(L_); lua_setmetatable(L_, tab); diff --git a/luprex/cpp/core/luastack.hpp b/luprex/cpp/core/luastack.hpp index 5cf11a4f..b2d0bd06 100644 --- a/luprex/cpp/core/luastack.hpp +++ b/luprex/cpp/core/luastack.hpp @@ -149,6 +149,7 @@ #include #include #include +#include #include "lua.h" #include "lauxlib.h" @@ -354,7 +355,6 @@ private: } void argerr(const char *arg, const char *tp) const; - public: LuaCoreStack(lua_State *L) : L_(L) {} @@ -368,43 +368,69 @@ public: int type(LuaSlot s) const { return lua_type(L_, s); } void checktype(LuaSlot s, int type) const { luaL_checktype(L_, s, type); } + + // These functions try to turn a Lua value into a C++ value. + // If the lua value doesn't match the desired type, then these return + // false or an empty optional. The ones that return bool only verify + // the value's type, they don't actually fetch the value. + + std::optional tryboolean(LuaSlot s) const; + std::optional tryinteger(LuaSlot s) const; + std::optional tryint(LuaSlot s) const; + std::optional trynumber(LuaSlot s) const; + std::optional trystring(LuaSlot s) const; + std::optional trystringview(LuaSlot s) const; + std::optional trythread(LuaSlot s) const; + std::optional trytoken(LuaSlot s) const; + std::optional tryxyz(LuaSlot s) const; + bool trytable(LuaSlot s) const; + bool trynil(LuaSlot s) const; + bool tryfunction(LuaSlot s) const; + bool trycfunction(LuaSlot s) const; + bool trytangible(LuaSlot s) const; + + // These functions turn a Lua value into a C++ value. + // If the lua value doesn't match the desired type, + // then these throw a lua error. The argname is used + // for making a nice error message. - bool istable(LuaSlot s) const { return lua_type(L_, s) == LUA_TTABLE; } - bool isstring(LuaSlot s) const { return lua_type(L_, s) == LUA_TSTRING; } - bool isnumber(LuaSlot s) const { return lua_type(L_, s) == LUA_TNUMBER; } - bool isinteger(LuaSlot s) const; - bool isint(LuaSlot s) const; - bool isthread(LuaSlot s) const { return lua_type(L_, s) == LUA_TTHREAD; } - bool isfunction(LuaSlot s) const { return lua_type(L_, s) == LUA_TFUNCTION; } - bool iscfunction(LuaSlot s) const { return lua_iscfunction(L_, s) != 0; } + bool ckboolean(LuaSlot s, const char *argname = "value") const; + lua_Integer ckinteger(LuaSlot s, const char *argname = "value") const; + int ckint(LuaSlot s, const char *argname = "value") const; + lua_Number cknumber(LuaSlot s, const char *argname = "value") const; + eng::string ckstring(LuaSlot s, const char *argname = "value") const; + std::string_view ckstringview(LuaSlot s, const char *argname = "value") const; + lua_State * ckthread(LuaSlot s, const char *argname = "value") const; + LuaToken cktoken(LuaSlot s, const char *argname = "value") const; + util::DXYZ ckxyz(LuaSlot s, const char *argname = "value") const; + void cktable(LuaSlot s, const char *argname = "value") const; + void cknil(LuaSlot s, const char *argname = "value") const; + void ckfunction(LuaSlot s, const char *argname = "value") const; + void ckcfunction(LuaSlot s, const char *argname = "value") const; + void cktangible(LuaSlot s, const char *argname = "value") const; + + // These functions check if a value can be converted + // to a C++ value. They don't actually return the C++ value. + // It is more efficient to use the 'try' or 'ck' functions above if + // you also want the value itself. + bool isboolean(LuaSlot s) const { return lua_type(L_, s) == LUA_TBOOLEAN; } - bool isnil(LuaSlot s) const { return lua_type(L_, s) == LUA_TNIL; } + bool isinteger(LuaSlot s) const { return bool(tryinteger(s)); } + bool isint(LuaSlot s) const { return bool(tryint(s)); } + bool isnumber(LuaSlot s) const { return lua_type(L_, s) == LUA_TNUMBER; } + bool isstring(LuaSlot s) const { return lua_type(L_, s) == LUA_TSTRING; } + bool isstringview(LuaSlot s) const { return lua_type(L_, s) == LUA_TSTRING; } + bool isthread(LuaSlot s) const { return lua_type(L_, s) == LUA_TTHREAD; } bool istoken(LuaSlot s) const { return lua_islightuserdata(L_, s) != 0; } - bool isxyz(LuaSlot s) const; + bool isxyz(LuaSlot s) const { return bool(tryxyz(s)); } + bool istable(LuaSlot s) const { return lua_type(L_, s) == LUA_TTABLE; } + bool isnil(LuaSlot s) const { return lua_type(L_, s) == LUA_TNIL; } + bool isfunction(LuaSlot s) const { return lua_isfunction(L_, s); } + bool iscfunction(LuaSlot s) const { return lua_iscfunction(L_, s) != 0; } + bool istangible(LuaSlot s) const { return trytangible(s); } + + - void checktable(LuaSlot s, const char *n) const { if (!istable(s)) argerr(n, "table"); } - void checkstring(LuaSlot s, const char *n) const { if (!isstring(s)) argerr(n, "string"); } - void checknumber(LuaSlot s, const char *n) const { if (!isnumber(s)) argerr(n, "number"); } - void checkinteger(LuaSlot s, const char *n) const { if (!isinteger(s)) argerr(n, "integer"); } - void checkint(LuaSlot s, const char *n) const { if (!isint(s)) argerr(n, "int"); } - void checkthread(LuaSlot s, const char *n) const { if (!isthread(s)) argerr(n, "thread"); } - void checkfunction(LuaSlot s, const char *n) const { if (!isfunction(s)) argerr(n, "function"); } - void checkcfunction(LuaSlot s, const char *n) const { if (!iscfunction(s)) argerr(n, "cfunction"); } - void checkboolean(LuaSlot s, const char *n) const { if (!isboolean(s)) argerr(n, "boolean"); } - void checknil(LuaSlot s, const char *n) const { if (!isnil(s)) argerr(n, "nil"); } - void checktoken(LuaSlot s, const char *n) const { if (!istoken(s)) argerr(n, "token"); } - void checkxyz(LuaSlot s, const char *n) const { if (!isxyz(s)) argerr(n, "xyz"); } - - bool ckboolean(LuaSlot s) const; - lua_Integer ckinteger(LuaSlot s) const; - int ckint(LuaSlot s) const; - lua_Number cknumber(LuaSlot s) const; - eng::string ckstring(LuaSlot s) const; - std::string_view ckstringview(LuaSlot s) const; - lua_State *ckthread(LuaSlot s) const; - LuaToken cktoken(LuaSlot s) const; - util::DXYZ ckxyz(LuaSlot s) const; - void clearmetatable(LuaSlot tab) const; void setmetatable(LuaSlot tab, LuaSlot mt) const; bool getmetatable(LuaSlot mt, LuaSlot tab) const; diff --git a/luprex/cpp/core/planemap.cpp b/luprex/cpp/core/planemap.cpp index e9a6dcce..fb6ebb46 100644 --- a/luprex/cpp/core/planemap.cpp +++ b/luprex/cpp/core/planemap.cpp @@ -835,34 +835,35 @@ void PlaneScan::configure(LuaKeywordParser &kp) { bool have_near = false; if (kp.parse(val, "plane")) { - LS.checkstring(val, "plane"); - plane_ = LS.ckstring(val); + plane_ = LS.ckstring(val, "plane"); have_plane = true; } if (kp.parse(val, "center")) { - LS.checkxyz(val, "center"); - util::DXYZ xyz = LS.ckxyz(val); + util::DXYZ xyz = LS.ckxyz(val, "center"); center_ = xyz; have_center = true; } if (kp.parse(val, "radius")) { - if (LS.isnumber(val)) { - radius_.x = LS.cknumber(val); - radius_.y = radius_.z = radius_.x; + auto simple = LS.trynumber(val); + if (simple) { + radius_ = *simple; have_radius = true; } else { - LS.checkxyz(val, "radius"); - util::DXYZ xyz = LS.ckxyz(val); - radius_ = xyz; - have_radius = true; + auto full = LS.tryxyz(val); + if (full) { + radius_ = *full; + have_radius = true; + } + } + if (!have_radius) { + luaL_error(L, "scan configuration: 'radius' must be a vector or number"); } } if (kp.parse(val, "shape")) { - LS.checkstring(val, "shape"); - eng::string shape = LS.ckstring(val); + eng::string shape = LS.ckstring(val, "shape"); if (shape == "box") { shape_ = BOX; } else if (shape == "sphere") { @@ -893,19 +894,17 @@ void PlaneScan::configure(LuaKeywordParser &kp) { } if (kp.parse(val, "include")) { - LS.checkboolean(val, "include"); if (!have_near) { luaL_error(L, "scan configuration: 'include' specified without 'near'"); } - include_near_ = LS.ckboolean(val); + include_near_ = LS.ckboolean(val, "include"); } if (kp.parse(val, "wholeplane")) { - LS.checkstring(val, "wholeplane"); if (have_plane || have_center || have_radius || have_shape) { luaL_error(L, "scan configuration: do not specify plane, center, shape, or radius with 'wholeplane'"); } - plane_ = LS.ckstring(val); + plane_ = LS.ckstring(val, "wholeplane"); set_whole_plane(); have_plane = true; have_center = true; diff --git a/luprex/cpp/core/table.cpp b/luprex/cpp/core/table.cpp index cb5f9d19..e162c1f6 100644 --- a/luprex/cpp/core/table.cpp +++ b/luprex/cpp/core/table.cpp @@ -8,7 +8,7 @@ // Does not thoroughly verify the vector. Returns the size // of the vector. static int check_vector_quick(LuaCoreStack &LS, LuaSlot vector, LuaSlot tmp) { - LS.checktable(vector, "vector"); + LS.cktable(vector, "vector"); int nkeys = LS.nkeys(vector); if (nkeys > 0) { LS.rawget(tmp, vector, nkeys); @@ -26,8 +26,8 @@ static int check_vector_quick(LuaCoreStack &LS, LuaSlot vector, LuaSlot tmp) { bool table_equal(LuaCoreStack &LS, LuaSlot t1, LuaSlot t2) { lua_State *L = LS.state(); int top = lua_gettop(L); - LS.checktable(t1, "table1"); - LS.checktable(t2, "table2"); + LS.cktable(t1, "table1"); + LS.cktable(t2, "table2"); int nkeys1 = lua_nkeys(L, t1.index()); int nkeys2 = lua_nkeys(L, t2.index()); if (nkeys1 != nkeys2) return false; @@ -262,7 +262,7 @@ LuaDefine(table_clear, "table,metaflag", LuaArg tab, clearmeta; LuaVar metatable, metafield; LuaDefStack LS(L, tab, clearmeta, metatable, metafield); - LS.checktable(tab, "table"); + LS.cktable(tab, "table"); if (LS.ckboolean(clearmeta)) { LS.getmetatable(metatable, tab); if (LS.istable(metatable)) { @@ -557,9 +557,11 @@ LuaDefine(deque_size, "deque", "return the number of items in the deque") { LuaArg deque; LuaRet size; LuaDefStack LS(L, deque, size); - LS.checktable(deque, "deque"); + LS.cktable(deque, "deque"); LS.rawget(size, deque, DEQUE_FILL); - LS.checknumber(size, "deque size"); + if (!LS.tryinteger(size)) { + luaL_error(L, "deque has been corrupted"); + } return LS.result(); } diff --git a/luprex/cpp/core/world-accessor.cpp b/luprex/cpp/core/world-accessor.cpp index 238a2caf..cc7cf082 100644 --- a/luprex/cpp/core/world-accessor.cpp +++ b/luprex/cpp/core/world-accessor.cpp @@ -444,7 +444,7 @@ LuaDefine(tangible_find, "config", "|circular area." "|" "|It is also valid to use math.huge (infinity) as a radius. For" - "|example, you could use shape='cylinder' and radiusz=math.huge" + "|example, you could use shape='cylinder' and radius.z=math.huge" "|to scan an infinitely tall cylinder." "|" "|If you are making a 2D game, it works fine to set all object Z" diff --git a/luprex/cpp/core/world-difftab.cpp b/luprex/cpp/core/world-difftab.cpp index b571b717..ae9e29ae 100644 --- a/luprex/cpp/core/world-difftab.cpp +++ b/luprex/cpp/core/world-difftab.cpp @@ -447,10 +447,10 @@ LuaDefine(table_diffcompare, "mtnmap,mtab,stnmap,stab", "for unit testing only") LuaVar tthread; LuaDefStack MLS(L, mtnmap, mtab, mstnmap, mstab, dbgstring, tthread); // Check the arguments. - MLS.checktable(mtnmap, "mtnmap"); - MLS.checktable(mstnmap, "mstnmap"); - MLS.checktable(mtab, "mtab"); - MLS.checktable(mstab, "mstab"); + MLS.cktable(mtnmap, "mtnmap"); + MLS.cktable(mstnmap, "mstnmap"); + MLS.cktable(mtab, "mtab"); + MLS.cktable(mstab, "mstab"); // Create a temporary thread to be the 'synch model'. We'll use the // existing thread as the 'master model'. Move two tables to the synch thread. @@ -477,9 +477,9 @@ LuaDefine(table_diffapply, "mtnmap,mtab,mstab", "for unit testing only") { LuaVar tthread, tangibles, mntmap, key, val; LuaDefStack MLS(L, mtnmap, mtab, mstab, eql, eqlstr, rtab, tthread, tangibles, mntmap, key, val); // Check the arguments. - MLS.checktable(mtnmap, "mtnmap"); - MLS.checktable(mtab, "mtab"); - MLS.checktable(mstab, "mstab"); + MLS.cktable(mtnmap, "mtnmap"); + MLS.cktable(mtab, "mtab"); + MLS.cktable(mstab, "mstab"); // Get the tangibles map. MLS.rawget(tangibles, LuaRegistry, "tangibles"); diff --git a/luprex/lua/uglyglobals.lua b/luprex/lua/uglyglobals.lua index 5ff8c19d..94e408eb 100644 --- a/luprex/lua/uglyglobals.lua +++ b/luprex/lua/uglyglobals.lua @@ -1,10 +1,10 @@ makeclass('ug') function ug.the() - return tangible.scan('globals',0,0,0,true)[1] + return tangible.find{plane="globals", center={0,0,0}, radius=1}[1] end -lis=tangible.scan('globals',0,0,0,true) +lis=tangible.find{plane="globals", center={0,0,0}, radius=1} if #lis==0 then local ugid=tangible.build{class='ug', animstate={xyz={0,0,0}, plane='globals'}} print("The global table is "..tangible.id(ugid)) @@ -42,8 +42,8 @@ function tabcat(t1,t2) -- Example: multistart(function(t) return tangible.id(t)%3==0 end,function() print("Tangible "..tangible.id(tangible.place()).." here") end) function multistart(fil,closure) - local lis=tangible.scan('nowhere',0,0,100,false) - tabcat(lis,tangible.scan('main',0,0,100,true)) + local lis=tangible.find{plane="nowhere", center={0,0,100}, radius=1} + tabcat(lis,tangible.find{plane="main", center={0,0,100}, radius=1}) local filter=type(fil)=='function' and fil or function(t) return isa(t,fil) end for _,t in ipairs(lis) do if filter(t) then tangible.start(t,closure) end end end