Age | Commit message (Collapse) | Author |
|
The discussions we've read about Lua classes conventionally use
ClassName:new() as the constructor, and so far we've followed that convention.
But setting metaclass(ClassName).__call = ClassName.new permits Lua to respond
to calls of the form ClassName(ctor args) by implicitly calling
ClassName:new(ctor args).
Introduce util.classctor(). Calling util.classctor(ClassName) sets ClassName's
metaclass's __call to ClassName's constructor method. If the constructor method
is named something other than new(), pass ClassName.method as the second arg.
Use util.classctor() on each of our classes that defines a new() method.
Replace ClassName:new(args) calls with ClassName(args) calls throughout.
|
|
in addition to a list {'name1', 'name2', ...}.
|
|
|
|
|
|
There are two conventions for Lua function calls. You can call a function with
positional arguments as usual:
f(1, 2, 3)
Lua makes it easy to handle omitted positional arguments: their values are nil.
But as in C++, positional arguments get harder to read when there are many, or
when you want to omit arguments other than the last ones.
Alternatively, using Lua syntactic sugar, you can pass a single argument which
is a table containing the desired function arguments. For this you can use
table constructor syntax to effect keyword arguments:
f{a=1, b=2, c=3}
A call passing keyword arguments is more readable because you explicitly
associate the parameter name with each argument value. Moreover, it gracefully
handles the case of multiple optional arguments. The reader need not be
concerned about parameters *not* being passed.
Now you're coding a Lua module with a number of functions. Some have numerous
or complicated arguments; some do not. For simplicity, you code the simple
functions to accept positional arguments, the more complicated functions to
accept the single-table argument style.
But how the bleep is a consumer of your module supposed to remember which
calling style to use for a given function?
mapargs() blurs the distinction, accepting either style. Coding a function
like this (where '...' is literal code, not documentation ellipsis):
function f(...)
local args = mapargs({'a', 'b', 'c'}, ...)
-- now use args.a, args.b, args.c
end
supports calls like:
f(1, 2, 3)
f{1, 2, 3}
f{c=3, a=1, b=2}
f{1, 2, c=3}
f{c=3, 1, 2} -- unlike Python!
In every call above, args.a == 1, args.b == 2, args.c == 3.
Moreover, omitting arguments (or explicitly passing nil, positionally or by
keyword) works correctly.
test_mapargs.lua exercises these cases.
|
|
so the user need not reverse-engineer the code to figure out the output.
|
|
|
|
Since timers presents a timers.Timer Lua class supporting queries and
cancellation, make TimersListener::scheduleAfter() and scheduleEvery() respond
immediately so the newly constructed Timer object has the reqid necessary to
perform those subsequent operations.
This requires that Lua invocations of these operations avoid calling the
caller's callback with that initial response.
Reinvent leap.generate() to return a Lua object supporting next() and done()
methods. A plain Lua coroutine that (indirectly) calls fiber.wait() confuses
the fiber scheduler, so avoid implementing generate() as a Lua coroutine.
Add a bit more leap.lua diagnostic output.
|
|
|
|
|
|
leap.eventstream() is used when we expect the viewer's LLEventAPI to send an
immediate first response with the reqid from the request, followed by some
number of subsequent responses bearing the same reqid. The difference between
eventstream() and generate() is that generate() expects the caller to request
each such response, whereas eventstream calls the caller's callback with each
response.
cancelreq() is for canceling the background fiber launched by eventstream()
before the callback tells it to quit.
Make WaitFor:close() remove the object from the waitfors list; similarly, make
WaitForReqid:close() remove the object from the pending list. For this reason,
cleanup() must iterate over a copy of each of the pending and waitfors lists.
Instead of unregisterWaitFor() manually searching the waitfors list, use
table.find().
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
script
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Add test_luafloater_demo2.lua and test_luafloater_gesture_list2.lua examples.
|
|
On Windows, std::filesystem::path::value_type is wchar_t, not char -- so
path::string_type is std::wstring, not std::string. So while Posix path
instances implicitly convert to string, Windows path instances do not. Add
explicit u8string() calls.
Also add LL.abspath() Lua entry point to further facilitate finding a resource
file relative to the calling Lua script. Use abspath() for both
test_luafloater_demo.lua and test_luafloater_gesture_list.lua.
|
|
This lets a calling script verify that it's running at the right point in the
viewer's life cycle. A script that wants to interact with the SL agent
wouldn't work if run from the viewer's command line -- unless it calls
startup.wait("STATE_STARTED"), which pauses until login is complete.
Modify test_luafloater_demo.lua and test_luafloater_gesture_list.lua to find
their respective floater XUI files in the same directory as themselves.
Make them both capture the reqid returned by the "showLuaFloater" operation,
and filter for events bearing the same reqid. This paves the way for a given
script to display more than one floater concurrently.
Make test_luafloater_demo.lua (which does not require in-world resources) wait
until 'STATE_LOGIN_WAIT', the point at which the viewer has presented the
login screen.
Make test_luafloater_gesture_list.lua (which interacts with the agent) wait
until 'STATE_STARTED', the point at which the viewer is fully in world.
Either or both can now be launched from the viewer's command line.
|
|
Earlier we had blithely designated the 'pending' list (which stores
WaitForReqid objects for pending request() and generate() calls) as a weak
table. But the caller of request() or generate() does not hold a reference to
the WaitForReqid object. Make pending hold "strong" references.
Private collections (pending, waitfors) and private scalars that are never
reassigned (reply, command) need not be entries in the leap table.
|
|
|
|
|
|
|
|
Don't use "debug" as the name of a function to conditionally write debug
messages: "debug" is a Luau built-in library, and assigning that name locally
would shadow the builtin. Use "dbg" instead.
Recast fiber.print_all() as fiber.format_all() that returns a string; then
print_all() is simply print(format_all()). This refactoring allows us to use
dbg(format_all()) as well.
Add a couple new dbg() messages at fiber state changes.
|
|
|
|
|
|
|
|
Also streamline util.contains(), given table.find().
|
|
fiber.lua's scheduler() is greedy, in the sense that it wants to run every
ready Lua fiber before retrieving the next incoming event from the viewer (and
possibly blocking for some real time before it becomes available). But check
for viewer shutdown before resuming any suspended-but-ready Lua fiber.
|
|
|
|
|
|
|
|
|
|
|
|
Recast fiber.yield() as internal function scheduler().
Move fiber.run() after it so it can call scheduler() as a local function.
Add new fiber.yield() that also calls scheduler(); the added value of this new
fiber.yield() over plain scheduler() is that if scheduler() returns before the
caller is ready (because the configured set_idle() function returned non-nil),
it produces an explicit error rather than returning to its caller. So the
caller can assume that when fiber.yield() returns normally, the calling fiber
is ready.
This allows any fiber, including the main thread, to call fiber.yield() or
fiber.wait(). This supports using leap.request(), which posts a request and
then waits on a WaitForReqid, which calls ErrorQueue:Dequeue(), which calls
fiber.wait().
WaitQueue:_wake_waiters() must call fiber.status() instead of
coroutine.status() so it understands the special token 'main'.
Add a new llluamanager_test.cpp test to exercise calling leap.request() from
Lua's main thread.
|
|
This fixes a hang if the Lua script explicitly calls fiber.run() before
LuaState::expr()'s implicit fiber.run() call.
Make fiber.run() remove the calling fiber from the ready list to avoid an
infinite loop when all other fibers have terminated: "You're ready!" "Okay,
yield()." "You're ready again!" ... But don't claim it's waiting, either,
because then when all other fibers have terminated, we'd call idle() in the
vain hope that something would make that one last fiber ready.
WaitQueue:_wake_waiters() needs to wake waiting fibers if the queue's not
empty OR it's been closed.
Introduce leap.WaitFor:close() to close the queue gracefully so that a looping
waiter can terminate, instead of using WaitFor:exception(), which stops the
whole script once it propagates. Make leap's cleanup() function call close().
Streamline fiber.get_name() by using 'or' instead of if ... then.
Streamline fiber.status() and fiber.set_waiting() by using table.find()
instead of a loop.
|
|
|
|
|
|
fiber.lua goes beyond coro.lua in that it distinguishes ready suspended
coroutines from waiting suspended coroutines, and presents a rudimentary
scheduler in fiber.yield(). yield() can determine that when all coroutines are
waiting, it's time to retrieve the next incoming event from the viewer.
Moreover, it can detect when all coroutines have completed and exit without
being explicitly told.
fiber.launch() associates a name with each fiber for debugging purposes.
fiber.get_name() retrieves the name of the specified fiber, or the running fiber.
fiber.status() is like coroutine.status(), but can return 'ready' or 'waiting'
instead of 'suspended'.
fiber.yield() leaves the calling fiber ready, but lets other ready fibers run.
fiber.wait() suspends the calling fiber and lets other ready fibers run.
fiber.wake(), called from some other coroutine, returns the passed fiber to
ready status for a future call to fiber.yield().
fiber.run() drives the scheduler to run all fibers to completion.
If, on completion of the subject Lua script, LuaState::expr() detects that the
script loaded fiber.lua, it calls fiber.run() to finish running any dangling
fibers. This lets a script make calls to fiber.launch() and then just fall off
the end, leaving the implicit fiber.run() call to run them all.
fiber.lua is designed to allow the main thread, as well as explicitly launched
coroutines, to make leap.request() calls. This part still needs debugging.
The leap.lua module now configures a fiber.set_idle() function that honors
leap.done(), but calls get_event_next() and dispatches the next incoming event.
leap.request() and generate() now leave the reqid stamp in the response. This
lets a caller handle subsequent events with the same reqid, e.g. for
LLLuaFloater.
Remove leap.process(): it has been superseded by fiber.run().
Remove leap.WaitFor:iterate(): unfortunately that would run afoul of the Luau
bug that prevents suspending the calling coroutine within a generic 'for'
iterator function.
Make leap.lua use weak tables to track WaitFor objects.
Make WaitQueue:Dequeue() call fiber.wait() to suspend its caller when the queue
is empty, and Enqueue() call fiber.wake() to set it ready again when a new
item is pushed.
Make llluamanager_test.cpp's leap test script use the fiber module to launch
coroutines, instead of the coro module. Fix a bug in which its drain()
function was inadvertently setting and testing the global 'item' variable
instead of one local to the function. Since some other modules had the same
bug, it was getting confused.
Also add printf.lua, providing a printf() function. printf() is short for
print(string.format()), but it can also print tables: anything not a number or
string is formatted using the inspect() function.
Clean up some LL_DEBUGS() output left over from debugging lua_tollsd().
|