Invoke can now only invoke method in class 'invoke', likewise for 'probe'

This commit is contained in:
2026-06-03 17:03:07 -04:00
parent 6658fb7940
commit e9c562eee5
12 changed files with 121 additions and 142 deletions

View File

@@ -13,5 +13,5 @@ RestoreOpenAssetTabsOnRestart=AlwaysRestore
AutoSaveWarningInSeconds=0
[/Script/Integration.lxProjectSettings]
ActiveServer=/Game/Luprex/KnownServers/SS_Localhost.SS_Localhost
ActiveServer=/Game/Luprex/KnownServers/SS_Standalone.SS_Standalone

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -15,7 +15,7 @@ The goal of this patch is to achieve a more consistent state of affairs. First,
Second, since the traceback will always contain the file and line number, and since there will always be a traceback, there's no reason to put the file and line number into the error message itself. We have taken steps to remove that feature. To accomplish this, the following patches to lua were made:
- removed calls to 'lua_where' (replacing them with 'lua_no_where')
- removed calls to 'addinfo' (replacing them with 'no_addinfo')
- removed calls to 'addinfo' (just commented out)
This patch is live and functioning.
@@ -74,17 +74,16 @@ This patch is live and is used implicitly whenever you iterate over a lua table.
## The Table Length Patch
I've changed the lua length operator so that when it is
applied to a table, it returns the number of keys in the
table. It does this in constant time. This change affects
lua_len, lua_rawlen, and the lua # operator.
I've changed the lua length operator so that when it is applied to a table, it
returns the number of keys in the table. It does this in constant time. This
change affects lua_len, lua_rawlen, and the lua # operator.
You might be wondering what the lua length operator
used to do? The lua documentation says this:
You might be wondering what the lua length operator used to do? The lua
documentation says this:
> The length operator applied on a table returns a border in
> that table. A border in a table t is any non-negative
> integer that satisfies the following condition:
> The length operator applied on a table returns a border in that table. A
> border in a table t is any non-negative integer that satisfies the following
> condition:
>
> (border == 0 or t[border] ~= nil) and
> (t[border + 1] == nil or border == math.maxinteger)
@@ -93,63 +92,53 @@ Those are *terrible* semantics:
- They're not useful for anything.
- It's not deterministic.
- In no sense of the word "length" is this the
length of the table.
- In no sense of the word "length" is this the length of the table.
Let me explain how that mess happened. They obviously
wanted the length operator to return the number of keys in
the table. Unfortunately, to count the number of keys in a
lua table actually takes O(N) time. So they came up with a
hack to make it faster: O(1). Unfortunately, the hack relies
on the table being a vector. That is, the table must have
numbered keys starting with 1. As long as you apply their
hack to a vector, it works perfectly and returns the
number of keys.
Let me explain how that mess happened. They obviously wanted the length
operator to return the number of keys in the table. Unfortunately, to count
the number of keys in a lua table actually takes O(N) time. So they came up
with a hack to make it faster: O(1). Unfortunately, the hack relies on the
table being a vector. That is, the table must have numbered keys starting with
1. As long as you apply their hack to a vector, it works perfectly and returns
the number of keys.
Unfortunately, if you apply the hacked length algorithm to a
table that isn't a vector, it doesn't work at all.
Unfortunately, if you apply the hacked length algorithm to a table that isn't
a vector, it doesn't work at all.
But I think the lua documentation didn't want to admit, "it
doesn't work at all." So instead, they invented this
concept of "a border" and pretended that was in some way a
helpful result. They should have just said, "the result is
But I think the lua documentation didn't want to admit, "it doesn't work at
all." So instead, they invented this concept of "a border" and pretended that
was in some way a helpful result. They should have just said, "the result is
undefined."
I had to change the table internal representation
for the table iterator patch (above). With the modified
table representation, returning the number of keys in the
table can be done in constant time, whether it's a vector or
not. So I changed the length operator to just return
the number of keys, full stop.
I had to change the table internal representation for the table iterator patch
(above). With the modified table representation, returning the number of keys
in the table can be done in constant time, whether it's a vector or not. So I
changed the length operator to just return the number of keys, full stop.
I've also added another function, lua_nkeys. This also
returns the number of keys in the table. It doesn't add any
functionality - I could use lua_rawlen and that would work
just as well. However, using lua_nkeys emphasizes the fact
that my code needs the *real* table length, not the "border"
bullshit that lua used to provide.
I've also added another function, lua_nkeys. This also returns the number of
keys in the table. It doesn't add any functionality - I could use lua_rawlen
and that would work just as well. However, using lua_nkeys emphasizes the
fact that my code needs the *real* table length, not the "border" bullshit
that lua used to provide.
This patch is live, and is necessary to the determinism of
the system.
This patch is live, and is necessary to the determinism of the system.
## The Table Flag Bits Patch
Our difference transmission algorithm does a recursive walk
of the tables in a given tangible. That recursive walk
requires a *visited* bit in each table. Of course, the lua
way of doing this would be to store the set of visited
tables as a separate table. But that would be a lot slower
than just setting a bit, and difference transmission is the
core of our system's performance bottleneck.
Our difference transmission algorithm does a recursive walk of the tables in a
given tangible. That recursive walk requires a *visited* bit in each table. Of
course, the lua way of doing this would be to store the set of visited tables
as a separate table. But that would be a lot slower than just setting a bit,
and difference transmission is the core of our system's performance
bottleneck.
We also need to store, in each table, a *table-type* enum.
We have several subtypes of tables: general tables, tangible
tables, class tables, and so forth. The difference
transmitter treats different types of tables differently.
We also need to store, in each table, a *table-type* enum. We have several
subtypes of tables: general tables, tangible tables, class tables, and so
forth. The difference transmitter treats different types of tables
differently.
This patch adds a 16-bit "flagbits" field to every Lua
table. We have added these lua API functions to access these
flag bits:
This patch adds a 16-bit "flagbits" field to every Lua table. We have added
these lua API functions to access these flag bits:
```cpp
uint16_t lua_getflagbits(lua_State *L, int index);
@@ -159,87 +148,74 @@ void lua_setflagbits(lua_State *L, int index, uint16_t flagbits);
void lua_modflagbits(lua_State *L, int index, uint16_t clearbits, uint16_t setbits);
```
The eris code for serializing the lua data structures has
been modified to save and restore the flagbits. Aside from
simply storing them, and saving and restoring them with
eris, the lua runtime doesn't do anything at all with the
The eris code for serializing the lua data structures has been modified to
save and restore the flagbits. Aside from simply storing them, and saving and
restoring them with eris, the lua runtime doesn't do anything at all with the
flagbits.
The Luprex engine has set aside four of the bits to store
the table-type enum. It has set aside one of the bits for
the 'visited' bit of the difference transmission algorithm.
The rest of the bits are currently unused.
The Luprex engine has set aside four of the bits to store the table-type enum.
It has set aside one of the bits for the 'visited' bit of the difference
transmission algorithm. The rest of the bits are currently unused.
This patch is live and in-use.
## The Insert Frame Patch
When we write C functions for Lua, we allocate a "stack
frame" on the lua stack. This is accomplished by class
LuaDefStack. See the document "Our In-House Lua API" for
more information.
When we write C functions for Lua, we allocate a "stack frame" on the lua
stack. This is accomplished by class LuaDefStack. See the document "Our
In-House Lua API" for more information.
This function has to insert some "nils" into the base of the
stack. The lua API does have a function that can do this,
but using it would be O(N^2). Since this functionality is
used in every single C function for Lua, we decided to
optimize things a little. We added a function to the lua API
that can do it in O(N) time. The name of the function is
lua_insert_frame, which sounds fancy, but all it does is
insert N "nils" at the bottom of the stack.
This function has to insert some "nils" into the base of the stack. The lua
API does have a function that can do this, but using it would be O(N^2). Since
this functionality is used in every single C function for Lua, we decided to
optimize things a little. We added a function to the lua API that can do it in
O(N) time. The name of the function is lua_insert_frame, which sounds fancy,
but all it does is insert N "nils" at the bottom of the stack.
This patch is live and is used in class LuaDefStack.
## The C++ Exceptions Patch
We've compiled lua to use C++ exceptions instead of longjmp.
The advantage of this is that if you do a lua_yield or
lua_error, any C++ destructors on the stack will get called.
We've compiled lua to use C++ exceptions instead of longjmp. The advantage of
this is that if you do a lua_yield or lua_error, any C++ destructors on the
stack will get called.
Although lua_yield and lua_error both throw C++ exceptions,
Lua cannot *deal with* C++ exceptions except for those it
generates itself. Therefore:
Although lua_yield and lua_error both throw C++ exceptions, Lua cannot *deal
with* C++ exceptions except for those it generates itself. Therefore:
- Never call the lua interpreter inside a C++ catch-block!
- Never throw an exception from inside a LuaDefine!
Exception 1: If you throw an uncaught exception, all that
does is terminate the program. It's always legal to
terminate the program.
Exception 1: If you throw an uncaught exception, all that does is terminate
the program. It's always legal to terminate the program.
Exception 2: If you throw an exception inside a LuaDefine
and then catch it inside the same LuaDefine, that's OK,
because the lua interpreter is not getting unwound.
Exception 2: If you throw an exception inside a LuaDefine and then catch it
inside the same LuaDefine, that's OK, because the lua interpreter is not
getting unwound.
Using C++ exceptions in lua_yield and lua_error means that
C++ destructors get called. Normally, calling destructors is
a good thing. However, there is one known case where this
causes issues: class LuaExtStack. Class LuaExtStack pushes
values onto the lua stack in its constructor, and later, in
its destructor, it pops those values back off.
Straightforward enough. But if you throw an error using the
lua_error function, then the error message is pushed on top
of the lua stack. If the throw triggers the LuaExtStack
destructor, then LuaExtStack will pop the stack, and in
doing so, it will unintentionally throw out the error
message. Oops.
Using C++ exceptions in lua_yield and lua_error means that C++ destructors get
called. Normally, calling destructors is a good thing. However, there is one
known case where this causes issues: class LuaExtStack. Class LuaExtStack
pushes values onto the lua stack in its constructor, and later, in its
destructor, it pops those values back off. Straightforward enough. But if you
throw an error using the lua_error function, then the error message is pushed
on top of the lua stack. If the throw triggers the LuaExtStack destructor,
then LuaExtStack will pop the stack, and in doing so, it will unintentionally
throw out the error message. Oops.
To fix this, we had to add a lua patch, which adds a new API
function "lua_isthrowing." This API function is used by the
LuaExtStack destructor, to decide whether to clean up the
stack or not. This new API function is not used anywhere
else in Luprex, and I do not expect it will ever be needed
anywhere else.
To fix this, we had to add a lua patch, which adds a new API function
"lua_isthrowing." This API function is used by the LuaExtStack destructor, to
decide whether to clean up the stack or not. This new API function is not used
anywhere else in Luprex, and I do not expect it will ever be needed anywhere
else.
This patch is live and is needed to keep lua error messages
working.
This patch is live and is needed to keep lua error messages working.
## The Object-Oriented Lua Patch
We have a patch to make lua object-oriented. To
really understand this patch, we need a separate
markdown file, "Object-Oriented-Lua.md". See
that file for an explanation.
We have a patch to make lua object-oriented. To really explain this patch, we
need a separate markdown file, "Object-Oriented-Lua.md". See that file for an
explanation.
## The Print No Address Patch (Unimplemented)
@@ -267,11 +243,10 @@ Update 2: I don't remember using userdata objects at all. I am not sure that Upd
## Token Literal Syntax Patch
Tokens are lightuserdata values encoding short alphanumeric
strings as base38 numbers (see `Tokens-A-New-Lua-Type.md`).
This patch adds a literal syntax to the Lua parser so that
tokens can be written directly in Lua source code using the
`@` prefix:
Tokens are lightuserdata values encoding short alphanumeric strings as base38
numbers (see `Tokens-A-New-Lua-Type.md`). This patch adds a literal syntax to
the Lua parser so that tokens can be written directly in Lua source code using
the `@` prefix:
```lua
local x = @null

View File

@@ -16,8 +16,6 @@ meant to be used for rapid update after the player invokes a thread on that one
* Do we really need can_be_controlled?
* Replace engio with 'invoke.' and 'probe.'
Secret / semi-secret variables
Secret functions

View File

@@ -43,9 +43,6 @@ bool UlxViewportClient::TryBringToFront(const FWidgetPath &Path)
bool UlxViewportClient::InputKey(const FInputKeyEventArgs &EventArgs)
{
UE_LOG(LogLuprexIntegration, Display, TEXT("UlxViewportClient::InputKey key=%s event=%d"),
*EventArgs.Key.ToString(), (int32)EventArgs.Event);
// Only act on left mouse button presses that bubbled up to the
// viewport unhandled. If the click landed on a descendant of a
// top-level widget in the root canvas, bring that top-level widget

View File

@@ -461,6 +461,11 @@ void World::probe_lua_call(int64_t actor_id, int64_t place_id, std::string_view
return;
}
if (classname != "probe") {
retvals->write_lua_value_type(LuaValueType::STRING);
retvals->write_string("currently, can only probe functions in class 'probe'");
}
LuaVar lclass, lfunc, actor, place, mt, tangibles, retvec, retval;
LuaExtStack LS(L, lclass, lfunc, actor, place, mt, tangibles, retvec, retval);
@@ -944,6 +949,9 @@ void World::invoke_lua_call(int64_t actor_id, int64_t place_id, std::string_view
} catch (const StreamException &ex) {
return;
}
// Currently, we only allow calling functions in class 'invoke'.
if (classname != "invoke") return;
{
lua_State *L = state();
@@ -966,8 +974,6 @@ void World::invoke_lua_call(int64_t actor_id, int64_t place_id, std::string_view
classname = LS.classname(lclass);
if (classname.empty()) return;
// TODO: CHECK FOR PERMIT_INVOKE.
// Get the function from the class.
LS.rawget(lfunc, lclass, funcname);
if (!LS.isfunction(lfunc)) return;

View File

@@ -29,9 +29,10 @@
--
----------------------------------------------------------------
makeclass('engio')
makeclass('probe')
makeclass('invoke')
function engio.gethotkeys()
function probe.gethotkeys()
local class = tangible.getclass(place)
-- if the tangible doesn't have a 'lookhotkeys' function, do nothing
@@ -48,7 +49,7 @@ function engio.gethotkeys()
return keys
end
function engio.presshotkey(pressed)
function invoke.presshotkey(pressed)
local class = tangible.getclass(place)
-- if the tangible doesn't have a 'lookhotkeys' function, do nothing

View File

@@ -1,6 +1,7 @@
makeclass("world")
makeclass('world')
makeclass('login')
makeclass("engio")
makeclass('probe')
makeclass('invoke')
makeclass('cube')
makeclass('sphere')
@@ -23,7 +24,7 @@ function world.init()
login.init()
end
function engio.move(action, xyz, facing)
function invoke.move(action, xyz, facing)
-- todo: sanity check the parameters.
tangible.animate{tan=actor, anim={action=action, interactive=true, xyz=xyz, facing=facing}}
end
@@ -54,7 +55,7 @@ function sphere.lookhotkeys(add)
end
function engio.getlookat()
function probe.getlookat()
local class = tangible.getclass(place)
-- if the tangible is not of any class, return empty string.

View File

@@ -29,9 +29,10 @@
--
----------------------------------------------------------------
makeclass('engio')
makeclass('probe')
makeclass('invoke')
function engio.getmenu()
function probe.getmenu()
local class = tangible.getclass(place)
-- if the tangible doesn't have a 'lookmenu' function, do nothing
@@ -47,7 +48,7 @@ function engio.getmenu()
return items
end
function engio.pressmenu(label)
function invoke.pressmenu(label)
local class = tangible.getclass(place)
-- if the tangible doesn't have a 'lookmenu' function, do nothing