Better documentation of the object-oriented-lua patch, and removal of an unused patch from lua

This commit is contained in:
2026-03-11 04:00:16 -04:00
parent 9fa6bd4bb6
commit d5fb9cd224
6 changed files with 407 additions and 88 deletions

View File

@@ -19,18 +19,6 @@ Second, since the traceback will always contain the file and line number, and si
This patch is live and functioning.
## Thread NextID Patch
We had the idea that Lua threads should contain an int64 field, "nextid", which theoretically was meant to help our system's unique ID allocator. We implemented a patch to store this field. When a Lua thread is created, the nextid field is initialized to zero. The following accessors were added to the Lua API:
```cpp
lua_Integer lua_getnextid(lua_State *L);
void lua_setnextid(lua_State *L, lua_Integer id);
```
This patch is dead code: as it turns out, we ended up not using this feature. These two accessors are never used anywhere in the Luprex codebase.
## Print Integers Patch
Lua numbers are actually double-precision floating point. There is no separate "int" data type. If you try to store an integer in lua, it gets stored as a double-precision float. That's usually just fine. Double precision can store integers up to 53 bits losslessly and precisely.
@@ -146,11 +134,22 @@ 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);
@@ -160,70 +159,87 @@ 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 flagbits.
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
Lua has a colon operator, for method lookup:
```lua
obj:method(arg1, arg2, arg3)
```
This looks for the method closure in *obj*. Instead of looking for the method in *obj*, I would like lua to look for the method in a separate table, the object's *class* table. One way to accomplish that is to use the *index* metamethod:
```lua
setmetatable(obj, { __index = class } )
```
That's not bad, but it puts both values and methods into the same namespace:
- Looking up data (eg, *obj.value)* will look for value in *obj*, and if it's not found, it will look for value in the *class* table. Looking for a value in the *class* table is pointless and inefficient.
- Looking up a method (eg, *obj:method*) will look for the closure in *obj* first, before looking in the *class* table. Looking for a closure in the data table *obj* is again pointless and inefficient.
- By putting both values and methods into the same namespace, we create the possibility of unintended mistakes.
I am thinking about implementing a new metatable entry: __METHODS = true. If this flag is present, then the colon operator *obj:method* looks for the method in the metatable, instead of looking for it in the object. With this new metamethod, the way to create a class would be to make a table full of methods, and then in that table, also put __METHODS = true. Then you would do this:
```lua
setmetatable(obj, class)
```
The class table would *be* the metatable. The result of this patch is that method lookup is done in the class table, value lookup is done in the object, and the two namespaces are kept separate.
Implementing this, as it turns out, is quite simple. The lua virtual machine contains an opcode, OP_SELF. This opcode is only used when the lua scripter uses the colon operator for method invocation. The opcode has two inputs: *obj*, and *method* (where method is a string, a method name). It returns the closure to call. Currently, OP_SELF just calls luaV_getttable to fetch the method from *obj*. We could easily replace the call to luaV_gettable with a function that we write ourselves, luaV_getmethod.
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.
## The Print No Address Patch (Unimplemented)