/** * @file lua_function.h * @author Nat Goodspeed * @date 2024-02-05 * @brief Definitions useful for coding a new Luau entry point into C++ * * $LicenseInfo:firstyear=2024&license=viewerlgpl$ * Copyright (c) 2024, Linden Research, Inc. * $/LicenseInfo$ */ #if ! defined(LL_LUA_FUNCTION_H) #define LL_LUA_FUNCTION_H #include "luau/luacode.h" #include "luau/lua.h" #include "luau/luaconf.h" #include "luau/lualib.h" #include "fsyspath.h" #include "llerror.h" #include "stringize.h" #include // std::uncaught_exceptions() #include // std::shared_ptr #include #include #include #include // std::pair class LuaListener; /***************************************************************************** * lluau namespace utility functions *****************************************************************************/ namespace lluau { // luau defines luaL_error() as void, but we want to use the Lua idiom of // 'return error(...)'. Wrap luaL_error() in an int function. #if __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wformat-security" #endif // __clang__ template int error(lua_State* L, const char* format, Args&&... args) { luaL_error(L, format, std::forward(args)...); #ifndef LL_MSVC return 0; #endif } #if __clang__ #pragma clang diagnostic pop #endif // __clang__ // luau removed lua_dostring(), but since we perform the equivalent luau // sequence in multiple places, encapsulate it. desc and text are strings // rather than string_views because dostring() needs pointers to nul- // terminated char arrays. int dostring(lua_State* L, const std::string& desc, const std::string& text); int loadstring(lua_State* L, const std::string& desc, const std::string& text); fsyspath source_path(lua_State* L); void set_interrupts_counter(lua_State *L, S32 counter); void check_interrupts_counter(lua_State* L); } // namespace lluau std::string lua_tostdstring(lua_State* L, int index); void lua_pushstdstring(lua_State* L, const std::string& str); LLSD lua_tollsd(lua_State* L, int index); void lua_pushllsd(lua_State* L, const LLSD& data); /***************************************************************************** * LuaState *****************************************************************************/ /** * RAII class to manage the lifespan of a lua_State */ class LuaState { public: typedef std::function script_finished_fn; LuaState(script_finished_fn cb={}); LuaState(const LuaState&) = delete; LuaState& operator=(const LuaState&) = delete; ~LuaState(); bool checkLua(const std::string& desc, int r); // expr() is for when we want to capture any results left on the stack // by a Lua expression, possibly including multiple return values. // int < 0 means error, and LLSD::asString() is the error message. // int == 0 with LLSD::isUndefined() means the Lua expression returned no // results. // int == 1 means the Lua expression returned one result. // int > 1 with LLSD::isArray() means the Lua expression returned // multiple results, represented as the entries of the array. std::pair expr(const std::string& desc, const std::string& text); operator lua_State*() const { return mState; } // Find or create LuaListener for this LuaState. LuaListener& obtainListener() { return obtainListener(mState); } // Find or create LuaListener for passed lua_State. static LuaListener& obtainListener(lua_State* L); private: script_finished_fn mCallback; lua_State* mState; std::string mError; }; /***************************************************************************** * LuaPopper *****************************************************************************/ /** * LuaPopper is an RAII class whose role is to pop some number of entries * from the Lua stack if the calling function exits early. */ class LuaPopper { public: LuaPopper(lua_State* L, int count): mState(L), mCount(count) {} LuaPopper(const LuaPopper&) = delete; LuaPopper& operator=(const LuaPopper&) = delete; ~LuaPopper(); void disarm() { set(0); } void set(int count) { mCount = count; } private: lua_State* mState; int mCount; }; /***************************************************************************** * LuaRemover *****************************************************************************/ /** * Remove a particular stack index on exit from enclosing scope. * If you pass a negative index (meaning relative to the current stack top), * converts to an absolute index. The point of LuaRemover is to remove the * entry at the specified index regardless of subsequent pushes to the stack. */ class LuaRemover { public: LuaRemover(lua_State* L, int index): mState(L), mIndex(lua_absindex(L, index)) {} LuaRemover(const LuaRemover&) = delete; LuaRemover& operator=(const LuaRemover&) = delete; ~LuaRemover() { lua_remove(mState, mIndex); } private: lua_State* mState; int mIndex; }; /***************************************************************************** * lua_function (and helper class LuaFunction) *****************************************************************************/ /** * LuaFunction is a base class containing a static registry of its static * subclass call() methods. call() is NOT virtual: instead, each subclass * constructor passes a pointer to its distinct call() method to the base- * class constructor, along with a name by which to register that method. * * The init() method walks the registry and registers each such name with the * passed lua_State. */ class LuaFunction { public: LuaFunction(const std::string_view& name, lua_CFunction function, const std::string_view& helptext); static void init(lua_State* L); static lua_CFunction get(const std::string& key); protected: using Registry = std::map>; using Lookup = std::map; static std::pair getRState() { return getState(); } private: static std::pair getState(); }; /** * lua_function(name, helptext) is a macro to facilitate defining C++ functions * available to Lua. It defines a subclass of LuaFunction and declares a * static instance of that subclass, thereby forcing the compiler to call its * constructor at module initialization time. The constructor passes the * stringized instance name to its LuaFunction base-class constructor, along * with a pointer to the static subclass call() method. It then emits the * call() method definition header, to be followed by a method body enclosed * in curly braces as usual. */ #define lua_function(name, helptext) \ static struct name##_luasub : public LuaFunction \ { \ name##_luasub(): LuaFunction(#name, &call, helptext) {} \ static int call(lua_State* L); \ } name##_lua; \ int name##_luasub::call(lua_State* L) // { // ... supply method body here, referencing 'L' ... // } /***************************************************************************** * lua_emplace(), lua_toclass() *****************************************************************************/ // Every instance of DistinctInt has a different int value, barring int // wraparound. class DistinctInt { public: DistinctInt(): mValue(++mValues) {} int get() const { return mValue; } operator int() const { return mValue; } private: static int mValues; int mValue; }; namespace { template struct TypeTag { // For (std::is_same), &TypeTag::value == &TypeTag::value. // For (! std::is_same), &TypeTag::value != &TypeTag::value. // And every distinct instance of DistinctInt has a distinct value. // Therefore, TypeTag::value is an int uniquely associated with each // distinct T. static DistinctInt value; }; template DistinctInt TypeTag::value; } // anonymous namespace /** * On the stack belonging to the passed lua_State, push a Lua userdata object * containing a newly-constructed C++ object T(args...). The userdata has a * Luau destructor guaranteeing that the new T instance is destroyed when the * userdata is garbage-collected, no later than when the LuaState is * destroyed. * * Usage: * lua_emplace(L, T constructor args...); * // L's Lua stack top is now a userdata containing T */ template void lua_emplace(lua_State* L, ARGS&&... args) { luaL_checkstack(L, 1, nullptr); int tag{ TypeTag::value }; if (! lua_getuserdatadtor(L, tag)) { // We haven't yet told THIS lua_State the destructor to use for this tag. lua_setuserdatadtor( L, tag, [](lua_State*, void* ptr) { // destroy the contained T instance static_cast(ptr)->~T(); }); } auto ptr = lua_newuserdatatagged(L, sizeof(T), tag); // stack is uninitialized userdata // For now, assume (but verify) that lua_newuserdata() returns a // conservatively-aligned ptr. If that turns out not to be the case, we // might have to discard the new userdata, overallocate its successor and // perform manual alignment -- but only if we must. llassert((uintptr_t(ptr) % alignof(T)) == 0); // Construct our T there using placement new new (ptr) T(std::forward(args)...); // stack is now initialized userdata containing our T instance -- return // that } /** * If the value at the passed acceptable index is a full userdata created by * lua_emplace(), return a pointer to the contained T instance. Otherwise * (index is not a full userdata; userdata is not of type T) return nullptr. */ template T* lua_toclass(lua_State* L, int index) { // get void* pointer to userdata (if that's what it is) void* ptr{ lua_touserdatatagged(L, index, TypeTag::value) }; // Derive the T* from ptr. If in future lua_emplace() must manually // align our T* within the Lua-provided void*, adjust accordingly. return static_cast(ptr); } /***************************************************************************** * lua_what() *****************************************************************************/ // Usage: std::cout << lua_what(L, stackindex) << ...; // Reports on the Lua value found at the passed stackindex. // If cast to std::string, returns the corresponding string value. class lua_what { public: lua_what(lua_State* state, int idx): L(state), index(idx) {} friend std::ostream& operator<<(std::ostream& out, const lua_what& self); operator std::string() const { return stringize(*this); } private: lua_State* L; int index; }; /***************************************************************************** * lua_stack() *****************************************************************************/ // Usage: std::cout << lua_stack(L) << ...; // Reports on the contents of the Lua stack. // If cast to std::string, returns the corresponding string value. class lua_stack { public: lua_stack(lua_State* state): L(state) {} friend std::ostream& operator<<(std::ostream& out, const lua_stack& self); operator std::string() const { return stringize(*this); } private: lua_State* L; }; /***************************************************************************** * LuaLog *****************************************************************************/ // adapted from indra/test/debug.h // can't generalize Debug::operator() target because it's a variadic template class LuaLog { public: template LuaLog(lua_State* L, ARGS&&... args): L(L), mBlock(stringize(std::forward(args)...)) { (*this)("entry ", lua_stack(L)); } // non-copyable LuaLog(const LuaLog&) = delete; LuaLog& operator=(const LuaLog&) = delete; ~LuaLog() { auto exceptional{ std::uncaught_exceptions()? "exceptional " : "" }; (*this)(exceptional, "exit ", lua_stack(L)); } template void operator()(ARGS&&... args) { LL_DEBUGS("Lua") << mBlock << ' '; stream_to(LL_CONT, std::forward(args)...); LL_ENDL; } private: lua_State* L; const std::string mBlock; }; #endif /* ! defined(LL_LUA_FUNCTION_H) */