summaryrefslogtreecommitdiff
path: root/indra/llcommon/lua_function.h
AgeCommit message (Collapse)Author
2024-09-20Add ability to pass command-line arguments to a Lua script.Nat Goodspeed
Introduce `ScriptCommand` class that parses a command line into a script name and optional args, using bash-like quoting and escaping. `ScriptCommand` searches for a file with that script name on a passed list of directories; the directories may be specified relative to a particular base directory. `ScriptCommand` supports the special case of a script name containing unescaped spaces. It guarantees that either the returned script file exists, or its `error()` string is non-empty. Replace `LLLeap::create()` logic, from which `ScriptCommand` was partly derived, with a `ScriptCommand` instance. Make `LLLUAmanager::runScriptFile()` use a `ScriptCommand` instance to parse the passed command line. Subsume `LLAppViewer::init()` script-path-searching logic for `--luafile` into `ScriptCommand`. In fact that lambda now simply calls `LLLUAmanager::runScriptFile()`. Make `lluau::dostring()` accept an optional vector of script argument strings. Following PUC-Rio Lua convention, pass these arguments into a Lua script as the predefined global `arg`, and also as the script's `...` argument. `LuaState::expr()` also accepts and passes through script argument strings. Change the log tag for the Lua script interruption message: if we want it, we can still enable it, but we don't necessarily want it along with all other "Lua" DEBUG messages. Remove `LuaState::script_finished_fn`, which isn't used any more. Also remove the corresponding `LLLUAmanager::script_finished_fn`. This allows us to simplify `~LuaState()` slightly, as well as the parameter signatures for `LLLUAmanager::runScriptFile()` and `runScriptLine()`.
2024-09-16WIP: edits in support of Lua script argsNat Goodspeed
2024-09-12Disable happy-path destructor semantics when unwinding C++ stack.Nat Goodspeed
If the C++ runtime is already handling an exception, don't try to launch more Lua operations.
2024-09-10Pass std::string_view by value, not by const reference.Nat Goodspeed
Consensus seems to be that (a) string_view is, in effect, already a reference, (b) it's small enough to make pass-by-value reasonable and (c) the optimizer can reason about values way better than it can about references.
2024-09-06Introduce LuaFeature debug setting, default off.Nat Goodspeed
Make central Lua engine functionality conditional on that flag.
2024-09-05Merge branch 'release/luau-scripting' into lua-resultsetNat Goodspeed
2024-08-28Ditch trailing spaces.Nat Goodspeed
2024-08-28Add `LL.setdtor()` function to add a "destructor" to any Lua object.Nat Goodspeed
`setdtor('description', object, function)` returns a proxy userdata object referencing object and function. When the proxy is garbage-collected, or at the end of the script, its destructor calls `function(object)`. The original object may be retrieved as `proxy._target`, e.g. to pass it to the `table` library. The proxy also has a metatable with metamethods supporting arithmetic operations, string concatenation, length and table indexing. For other operations, retrieve `proxy._target`. (But don't assign to `proxy._target`. It will appear to work, in that subsequent references to `proxy._target` will retrieve the replacement object -- however, the destructor will still call `function(original object)`.) Fix bugs in `lua_setfieldv()`, `lua_rawgetfield()` and `lua_rawsetfield()`. Add C++ functions `lua_destroyuserdata()` to explicitly destroy a `lua_emplace<T>()` userdata object, plus `lua_destroybounduserdata()`. The latter can bind such a userdata object as an upvalue to pass to `LL.atexit()`. Make `LL.help()` and `LL.leaphelp()` help text include the `LL.` prefix.
2024-08-20Fix for #2237: intermittent Lua data stack overflow.Nat Goodspeed
Use a static unordered_map to allow a function receiving (lua_State* L) to look up the LuaState instance managing that lua_State. We've thought about this from time to time already. LuaState's constructor creates the map entry; its destructor removes it; the new static getParent(lua_State* L) method performs the lookup. Migrate lluau::set_interrupts_counter() and check_interrupts_counter() into LuaState member functions. Add a new mInterrupts counter for them. Importantly, LuaState::check_interrupts_counter(), which is indirectly called by a lua_callbacks().interrupt function, no longer performs any Lua stack operations. Empirically, it seems the Lua engine is capable of interrupting itself at a moment when re-entry confuses it. Change previous lluau::set_interrupts_counter(L, 0) calls to LuaState::getParent(L).set_interrupts_counter(0). Also add LuaStackDelta class, and a lua_checkdelta() helper macro, to verify that the Lua data stack depth on exit from a block differs from the depth on entry by exactly the expected amount. Sprinkle lua_checkdelta() macros in likely places.
2024-08-15Introduce lluau_checkstack(L, n); use instead of luaL_checkstack().Nat Goodspeed
luaL_checkstack() accepts a third parameter which is included in the stack overflow error message. We've been passing nullptr, leading to messages of the form "stack overflow ((null))". lluau_checkstack() implicitly passes __FUNCTION__, so we can distinguish which underlying luaL_checkstack() call encountered the stack overflow condition. Also, when calling each atexit() function, pass Luau's debug.traceback() function as the lua_pcall() error handler. This should help diagnose errors in atexit() functions.
2024-08-01Add lua_push(), lua_to(), lua_[gs]etfieldv(), lua_raw[gs]etfield().Nat Goodspeed
Leverage C++ overloads to allow use of generic function names disambiguated by argument type. This allows using templates for certain common operation sequences.
2024-07-10Remove ability to reuse a LuaState between LLLUAmanager functions.Nat Goodspeed
Remove LLLUAmanager::mumbleScriptLine() LuaState& parameters. Make startScriptLine(), waitScriptLine() and runScriptLine() exactly parallel to startScriptFile(), waitScriptFile() and runScriptFile(). That means that runScriptLine()'s C++ coroutine instantiates and destroys its own LuaState, which means that LL.atexit() functions will run on the Lua-specific C++ coroutine rather than (say) the viewer's main coroutine. Introduce LLLUAmanager::script_result typedef for std::pair<int, LLSD> and use in method returns. Remove LuaState::initLuaState(); move its logic back into the constructor. Remove initLuaState() calls in the expr() error cases: they're moot now that we won't get subsequent expr() calls on the same LuaState instance. Remove LLFloaterLUADebug "Use clean lua_State" checkbox and the cleanLuaState() method. Remove mState member. Remove explicit LuaState declarations from LLLUAmanager tests. Adapt one test for implicit LuaState: it was directly calling LuaState::obtainListener() to discover the LuaListener's reply-pump name. But since that test also captures two leap.request() calls from the Lua script, it can just look at the "reply" key in either of those requests.
2024-07-02Promote LuaRemover from llluamanager.cpp to lua_function.h.Nat Goodspeed
2024-06-27Introduce TypeTag<T> template whose int value differs for each T.Nat Goodspeed
This replaces type_tag<T>(), which searched and possibly extended the type_tags unordered_map at runtime. If we called lua_emplace<T>() from different threads, that would require locking type_tags. In contrast, the compiler must instantiate a distinct TypeTag<T> for every distinct T passed to lua_emplace<T>(), so each gets a distinct value at static initialization time. No locking is required; no lookup; no allocations. Add a test to llluamanager_test.cpp to verify that each distinct T passed to lua_emplace<T>() gets its own TypeTag<T>::value, and that each gets its own destructor -- but that different lua_emplace<T>() calls with the same T share the same TypeTag<T>::value and the same destructor.
2024-06-27Make lua_emplace<T>() use Luau userdata tags with destructors.Nat Goodspeed
It turns out that Luau does not honor PUC-Rio Lua's __gc metafunction, so despite elaborate measures, the previous lua_emplace<T>() implementation would not have destroyed the contained C++ T object when the resulting userdata object was garbage-collected. Moreover, using LL.atexit() as the mechanism to destroy lua_emplace<T>() userdata objects (e.g. LuaListener) would have been slightly fragile because we also want to use LL.atexit() to make the final fiber.run() call, when appropriate. Introducing an order dependency between fiber.run() and the LuaListener destructor would not be robust. Both of those problems are addressed by leveraging one of Luau's extensions over PUC-Rio Lua. A Luau userdata object can have an int tag; and a tag can have an associated C++ destructor function. When any userdata object bearing that tag is garbage-collected, Luau will call that destructor; and Luau's lua_close() function destroys all userdata objects. The resulting lua_emplace<T>() and lua_toclass<T>() code is far simpler. It only remains to generate a distinct int tag value for each different C++ type passed to the lua_emplace<T>() template. unordered_map<std::type_index, int> addresses that need.
2024-06-18Use LL_DEBUGS("Lua") for LuaLog.Nat Goodspeed
We might decide to leave some of them in place.
2024-06-18lua_emplace<T>() should permit GC despite LL.atexit() safety net.Nat Goodspeed
lua_emplace<T>() was passing LL.atexit() a closure binding the new userdata with a cleanup function. The trouble with that was that a strong reference to the new userdata would prevent it ever being garbage collected, even if that was the only remaining reference. Instead, create a new weak table referencing the userdata, and bind that into the cleanup function's closure. Then if the only remaining reference to the userdata is from the weak table, the userdata can be collected. Make lua_emplace_call_gc<T>() check the bound weak table in case the userdata has in fact been collected. Also, in lua_toclass<T>(), use luaL_checkudata() to synopsize comparing the putative userdata's metatable against the one synthesized by lua_emplace<T>(). This saves several explicit steps.
2024-06-17Store script's LuaListener in userdata in lua_State's Registry.Nat Goodspeed
Instead of deriving LuaListener from LLInstanceTracker with an int key, generating a unique int key and storing that key in the Registry, use new lua_emplace<LuaState>() to store the LuaListener directly in a Lua userdata object in the Lua Registry. Because lua_emplace<T>() uses LL.atexit() to guarantee that ~LuaState will destroy the T object, we no longer need ~LuaState() to make a special call specifically to destroy the LuaListener, if any. So we no longer need LuaState::getListener() separate from obtainListener(). Since LuaListener is no longer an LLInstanceTracker subclass, make LuaState::obtainListener() return LuaListener& rather than LuaListener::ptr_t.
2024-06-14Introduce LL.atexit(), internal lua_emplace<T>(), lua_toclass<T>().Nat Goodspeed
Publish new LL.atexit() function that accepts a Lua function (or C++ closure) and saves it (in Registry["atexit"] table) to call later. Make ~LuaState() walk the Registry["atexit"] table, if it exists, calling each function appended to that table. (Consider using that mechanism to clean up a LuaListener, if one was instantiated. Possibly also use for p.s. leap.run()? But that's run after every expr() call, instead of only at ~LuaState() time. Pragmatically, though, the distinction only matters for a LUA Debug Console LUA string with "clean lua_State" unchecked.) For use by future lua_function() entry points, lua_emplace<T>(ctor args...) pushes a Lua userdata object containing a newly-constructed T instance -- actually a std::optional<T> to avoid double destruction. lua_emplace<T>() is specifically intended to be usable even for T with a nontrivial destructor: it gives the userdata a metatable with a __gc function that destroys the contained T instance when the userdata is garbage collected. But since garbage collection doesn't guarantee to clean up global variables with __gc methods, lua_emplace<T>() also uses LL.atexit() to ensure that ~T() will run when the LuaState is destroyed. The companion to lua_emplace<T>() is lua_toclass<T>(), which returns a non-nullptr T* if the referenced index is in fact a userdata created by lua_emplace<T>() for the same T, that has not yet been destroyed. This lets C++ code access a T previously embedded in Lua userdata.
2024-04-18Move {set,check}_interrupts_counter() to lluau namespace.Nat Goodspeed
Use in LuaState::expr() so we can catch a runaway in-memory Lua chunk as well as a script read from a file.
2024-04-03Introduce fsyspath subclass of std::filesystem::path.Nat Goodspeed
Our std::strings are UTF-8 encoded, so conversion from std::string to std::filesystem::path must use UTF-8 decoding. The native Windows std::filesystem::path constructor and assignment operator accepting std::string use "native narrow encoding," which mangles path strings containing UTF-8 encoded non-ASCII characters. fsyspath's std::string constructor and assignment operator explicitly engage std::filesystem::u8path() to handle encoding. u8path() is deprecated in C++20, but once we adapt fsyspath's conversion to C++20 conventions, consuming code need not be modified.
2024-04-02Add LL.source_path(), source_dir() Lua entry points.Nat Goodspeed
This helps a Lua script log its own identity, or find associated files relative to its location in the filesystem. Add more comprehensive logging around the start and end of a given Lua script, or its "p.s." fiber.run() call.
2024-03-28Move our lua_register(), lua_rawlen() from lua_function.h to .cpp.Nat Goodspeed
2024-02-29Refactor require() to make it easier to reason about Lua stack usage.Nat Goodspeed
Push throwing Lua errors down into LLRequireResolver::findModule() and findModuleImpl() so their callers don't have to handle the error case. That eliminates finishrequire(). require() itself now only retrieves (and pops) the passed module name and calls LLRequireResolver::resolveRequire() to do the actual work. resolveRequire() is now void. It only instantiates LLRequireResolver and calls its findModule(). findModule() is now also void. It's guaranteed to either push the loaded Lua module or throw a Lua error. In particular, when findPathImpl() cannot find the specified module, findModule() throws an error. That replaces ModuleStatus::NotFound. Since std::filesystem::path::append() aka operator/() detects when its right operand is absolute and, in that case, discards the left operand, we no longer need resolveAndStoreDefaultPaths(): we can just invoke that operation inline. When findModule() pushes _MODULES on the Lua stack, it uses LuaRemover (below) to ensure that _MODULES is removed again no matter how findModules() exits. findModuleImpl() now accepts the candidate pathname as its argument. That eliminates mAbsolutePath. findModuleImpl() now returns only bool: true means the module was found and loaded and pushed on the Lua stack, false means not found and nothing was pushed; no return means an error was reported. Push running a newly found module's source file down into findModuleImpl(). That eliminates the distinction between Cached and FileRead, which obviates ModuleStatus: a bool return means either "previously cached" or "we read it, compiled it, loaded it and ran it." That also eliminates the need to store the module's textual content in mSourceCode. Similarly, once loading the module succeeds, findModuleImpl() caches it in _MODULES right away. That eliminates ResolvedRequire since we need not pass the full pathname of the found module (or its contents) back up through the call chain. Move require() code that runs the new module into private runModule() method, called by findModuleImpl() in the not-cached case. runModule() is the only remaining method that can push either a string error message or the desired module, because of its funny stack manipulations. That means the check for a string error message on the stack top can move down to findModuleImpl(). Add LuaRemover class to ensure that on exit from some particular C++ block, the specified Lua stack entry will definitely be removed. This is different from LuaPopper in that it engages lua_remove() rather than lua_pop(). Also ditch obsolete await_event() Lua entry point.
2024-02-27Merge branch 'release/luau-scripting' into luau-require-impl.Nat Goodspeed
2024-02-23require() code clean-upMnikolenko Productengine
2024-02-23Ditch DebugExit: we already have Debug (in debug.h)Nat Goodspeed
2024-02-21Add the option to use clean lua_State in "Lua debug" floaterMnikolenko Productengine
2024-02-20Initial require implementationMnikolenko Productengine
2024-02-13Add leaphelp() Lua builtin function for help on LEAP operations.Nat Goodspeed
leaphelp() (no argument) shows a list of all LEAP APIs. leaphelp(API) shows further help for a specific API. Both forms query LuaListener's LeapListener and report its responses. In future we might reimplement leaphelp() as a Lua function. Add LuaState::getListener() method, which checks whether there's a LuaListener associated with this LuaState and returns a pointer if so. Add LuaState::obtainListener() method, which finds or creates a LuaListener for this LuaState and returns its pointer. Both the above use logic migrated from the Lua listen_events() entry point, which now calls obtainListener() instead.
2024-02-13Add help() function to Lua "builtins."Nat Goodspeed
help() with no argument lists all our viewer builtins. help(function, function, ...) shows help text for each named function. Each argument can be either a string or the function in question (e.g. help(help)). To support Lua-related text containing line breaks, make LLTextEditor:: pasteTextWithLinebreaks() a public template method. Change the existing implementation, which specifically accepts (const LLWString&), into its LLWString specialization. The generic template passes llconvert(arg) to that specialization, the one real implementation. Make LLFloaterLUADebug methods call pasteTextWithLinebreaks() instead of insertText(), which ignores newline characters. To allow help() to accept an actual function as well as a string name, add a lookup-by-function-pointer map to LuaFunction. (A Lua function does not store a name.) Make the constructor store an entry in the new lookup map as well as in the original registry map. Change LuaFunction::getRegistry() and getRegistered() to getState() and getRState(), respectively. Each returns a std::pair, but the first binds non-const references while the second binds const references.
2024-02-12WIP: Changes towards supporting Lua console help text.Nat Goodspeed
2024-02-09Change the LuaFunction subclass instance name suffix to _lua.Nat Goodspeed
We add a suffix to let us publish a Lua foo() function that wraps a C++ foo() function. Of course the lua_CFunction must accept lua_State* and extract its parameters from the Lua stack, so it must invoke different C++ code than the C++ foo() function it's trying to reach. So the lua_CFunction is a method of the LuaFunction subclass instance named foo_lua. The suffix was _luadecl, but since the class name shows up in log messages, make it the more streamlined _lua instead.
2024-02-08Add required helptext parameter to lua_function() macro.Nat Goodspeed
Extend the LuaFunction::Registry map to store helptext as well as the function pointer. Add help text to every existing lua_function() invocation.
2024-02-08Defend lluau::error() from platform-specific diagnostics.Nat Goodspeed
macOS clang produces fatal warnings when trying to pass a const char* parameter to luaL_error() (-Wformat-security). Temporarily suppressing that requires #pragma clang directives which, in turn, produce fatal warnings in VS. Moreover, VS recognizes that luaL_error() never returns, and so diagnoses the following return statement as unreachable code. But that return statement is the whole reason for lluau::error()'s existence...
2024-02-07Add machinery to capture result of running a Lua script or snippet.Nat Goodspeed
Add LuaState::expr() that evaluates a Lua snippet and reports back any result (or error) left on the stack. Add LLLUAmanager::runScriptFile() and runScriptLine() overloads that accept a callback with an (int count, LLSD result) signature. The count disambiguates (error, no result, one result, array of results). Also add overloads that accept an existing LuaState instance. Also add waitScriptFile() and waitScriptLine() methods that pause the calling coroutine until the Lua script completes, and return its results. Instead of giving LuaState a description to use for all subsequent checkLua() calls, remove description from its constructor and data members. Move to expr() and checkLua() parameters: we want a description specific to each operation, rather than for the LuaState as a whole. This prepares for persistent LuaState instances. For now, the existing script_finished_fn semantics remain: the callback will be called only when the LuaState is destroyed. This may need to change as we migrate towards longer-lasting LuaState instances. Make lua_function(name) macro append suffixes to the name for both the LuaFunction subclass declaration and the instance declaration. This allows publishing a lua_function() name such as sleep(), which already has a different C++ declaration. Move the Lua sleep() entry point to a standalone lua_function(sleep), instead of a lambda in the body of runScriptFile().
2024-02-06Break out lua_function.h,.cpp and lualistener.h,.cpp.Nat Goodspeed
The intention is to decentralize Luau entry points into our C++ code, permitting a given entry point to be added to the .cpp file that already deals with that class or functional area. Continuing to add every such entry point to llluamanager.cpp doesn't scale well. Extract LuaListener class from llluamanager.cpp to its own header and .cpp file. Extract from llluamanager into lua_function.h (and .cpp) declarations useful for adding a lua_function Luau entry point, e.g.: lua_register() lua_rawlen() lua_tostdstring() lua_pushstdstring() lua_tollsd() lua_pushllsd() LuaPopper lua_function() and LuaFunction class LuaState lua_what lua_stack DebugExit