Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Integrate Luau scripting into the viewer. #785

Closed
wants to merge 1,064 commits into from

Conversation

nat-goodspeed
Copy link
Collaborator

Some features were added in secondlife/viewer-private#20, supplement generated release notes with those.

@nat-goodspeed
Copy link
Collaborator Author

recheck

@maxim-productengine maxim-productengine added this to the Lua milestone Mar 26, 2024
popper.disarm();
// Table keys are all integers: are they reasonable integers?
// Arbitrary max: may bite us, but more likely to protect us
size_t array_max{ 10000 };
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe you an constexpr or const that. This will make the intent of it being some constant upper limit much clearer

}
// right away expand the result array to the size we'll need
LLSD result{ LLSD::emptyArray() };
result[highkey - 1] = LLSD();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this really safe, again my lack of LUA-FU is obivious, but what if I construct a table with just one entry.
Lower bound will be equal to higher bound

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This -1 is to adjust for the difference between Lua 1-relative indexing and C++ 0-relative indexing. If the high key of a Lua array is 10, there are 10 entries. To immediately reserve that much space in an LLSD array, we assign to result[9].

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is correct, buy my idle musing had been about a 1 length table, it would still not crash I assume. But highkey would be equal to lowkey and thus in result the first item would be overwritten with a LLSD()

As long as that's possible, coming here with a table of size 1

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I stipulate that this is a lot of code to wade through, though I find myself doubting that breaking out subsections would make it easier to follow. The basic idea, though, is that we tackle arrays in two passes. The first pass just validates and collects keys. Then we validate that there aren't huge gaps (to avoid having to allocate a ridiculously large contiguous LLSD array) and find the highkey. All this is before storing any values.

Finally we grow the LLSD result array with a trivial assignment and then make a whole second pass to populate its values.

All this is because Lua refuses to guarantee the order in which lua_next() retrieves key/value pairs.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can attest that you did a very good in (in the code comments) to describe what happens and how such a array is "inflated" post construction.

But my worry is still not really addressed (I think). Which is being able to hand craft some LUA code that would exploit this logic.
Which means I cannot let go yet, sorry for that :)

What would help if you got an lua example table (LUA_TTABLE) that cam be converted.
I think it would look something like this?

local tbl {}
tbl[1] = valx
tbl[20] = valy

Which then would increase the final C++ object from 2-19 with empty values.

Now my concern (valid or not, I do not know) is this

local tbl {}
tbl[1] = valx

Table with one element, consequently after parsing it would just overwrite valx with an empty LLSD

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't yet understand the problem situation, sorry. If either the first tbl or the second tbl is passed to lua_tollsd(), I think it will work correctly. But those two tables would be two distinct calls to lua_tollsd(). The table passed into a single call to lua_tollsd() wouldn't change shape during the function, because it's a synchronous call from the Lua VM.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There might be in fact no issue at all. bur rather me having a misconception how the LUA VM does interact with the C++ code base.

I will have to step through that with a debugger at least once, this should give me some clarity. Meanwhile it is probably best to assume "there is no bug" (okay there is always a bug, but likely not right here)

// thread, userdata) are not convertible to LLSD, indicating a coding
// error in the caller.
return lluau::error(L, "Cannot convert type %s to LLSD", luaL_typename(L, index));
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A bit of refactoring (pulling case construct into their own functions) would help a great to deal make this code more reader friendly

// if we got a LuaListener instance, destroy it
auto lptr{ listener.get() };
listener.reset();
delete lptr;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like some back magic, I suppose listener is a smart pointer rather than a doof-pointer. Why is either reset or assigning it a nullptr not enough to diospose it?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess it is magic: LLInstanceTracker magic. Arguably there should be an LLInstanceTracker::destroy() method or some such to encapsulate.

LLInstanceTracker stores a map of neutered std::shared_ptrs with no-op deleters. The point is that LLInstanceTracker is only to find subclass objects, not to manage their lifespan. That's what necessitates the logic in question.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it. How do I have to bribe you, to add a bit of documentation about this behavior into the code?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The best approach would be to add the aforementioned LLInstanceTracker::destroy() method, so that its internal comments explain its implementation. I'll do that.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

// its internal error status. Any subsequent lua_pcall() calls
// with this lua_State will report error regardless of whether the
// chunk runs successfully. Get a new lua_State().
initLuaState();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd argue for some LL_WARNS here and down there (533)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great minds! I added one here and down there with my most recent PR.

LuaListener::ptr_t listener;
// Does this lua_State already have a LuaListener stored in the registry?
auto keytype{ lua_getfield(L, LUA_REGISTRYINDEX, "event.listener") };
llassert(keytype == LUA_TNIL || keytype == LUA_TNUMBER);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be better to either use llassert_always or handle it gracefully. The latter being more enduser friendly

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a "should never happen" condition: either a coder error, or the Lua registry got badly trashed. It's me being cautious about venturing into new turf.

LuaFunction::LuaFunction(const std::string_view& name, lua_CFunction function,
const std::string_view& helptext)
{
const auto& [registry, lookup] = getState();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see this syntax, it makes me happy

}

/*****************************************************************************
* lua_what
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lua? WHAT?!?

// 'return error(...)'. Wrap luaL_error() in an int function.
#if __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wformat-security"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang isn't all wrong about this. Can a user controlled string enter here or will all of this be sanitized?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, users would directly call Luau's error() function. This is for our internal use.

What I would like to know is what magic attribute to associate with the const char* format argument to (a) direct the compiler's static validation to callers of this error() function, and (b) pass through the statically-validated format to the underlying function.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If there is no "user" (user in this case could also be resources loaded from XML) supplied char const* (and I understood there is no such thing), then it is indeed strange that clang gets so lippy.

For some warnings compiler will try track arguments during the function calls and then only warn if they can determine the error. As this function is a template there is a good change clang will do it just like that (this would be visible in the way clang emits the warning).

A plausible explanation could then be attributed to the lua precompiled libs somehow in the call stack, then there can be no verification > error.

For b) then compiling lua inside the viewer source tree could fix it (not a super good solution).

Long story short: I think the pragma is the most pragmatic way then. (Except it will not help with GCC)

@nat-goodspeed
Copy link
Collaborator Author

nat-goodspeed commented Aug 16, 2024

For testing staging release:

build = https://github.com/secondlife/viewer/releases/tag/Second_Life_Test%23f8c70f4
cohort = Second Life Project Lua
desired = 10

cosmic-linden and others added 9 commits September 3, 2024 13:56
#2483: Fix label typo in PBR terrain texture transforms tab
When asked to retrieve a slice starting at an `index > 0`, `getSliceStart()` was
returning an LLSD array whose first `index` entries were `isUndefined()`,
followed by the desired data. Fix to omit those undefined entries.
Fix visualizing luminance buffer and small cleanup
Change `result_view()` from a simple function to a callable table so we can
add conventional/default functions to it: `result_view.fetch()` is a generic
`fetch()` function suitable for use with `result_view()`, and `result_view.close()`
is a variadic function that closes result sets for whichever keys are passed.

This arises from the fact that any `LL::ResultSet` subclass is accessed
generically through its base class, therefore we don't need distinct
"getSlice" and "closeResult" operations for different `LLEventAPI` listeners.
(It might make sense to relocate those operations to a new generic listener,
but for now "LLInventory" works.)

That lets `result_view()`'s caller omit the `fetch` parameter unless it
requires special behavior. Omitting it uses the generic `result_view.fetch()`
function.

Moreover, every view returned by `result_view()` now contains a close()
function that closes that view's result set.

The table returned by LLInventory.lua's `result()` function has a `close()`
method; that method can now call `result_view.close()` with the two keys of
interest. That table's `__index()` metamethod can now leverage `result_view()`'s
default `fetch` function.
This is the query that produced so many results that, before we lifted the
infinite-loop interrupt limit, inspect(result) hit the limit and terminated.
nat-goodspeed and others added 25 commits September 20, 2024 16:34
to put the biggest performance hits at the top of the list.
Now the location to which to teleport and the camera focus point can both be
specified by the caller, in this case the frame_profile bash script.
Reverse the sort order for profile_cmp.py
Add ability to pass command-line arguments to a Lua script.
# Conflicts:
#	indra/newview/llfeaturemanager.cpp
#	indra/newview/llviewertexturelist.cpp
#	indra/newview/llvoicewebrtc.cpp
Maintenance B merges into develop
Merge develop into release/luau-scripting
@github-actions github-actions bot locked and limited conversation to collaborators Oct 29, 2024
@nat-goodspeed
Copy link
Collaborator Author

Since the long-running Lua project branch has been merged into develop, this PR has become moot.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.