2026-02-22 02:12:28 -05:00
|
|
|
# Our In-House Lua API
|
|
|
|
|
|
|
|
|
|
Lua comes with a C API. In general, the functions in this
|
|
|
|
|
API use the following calling convention: push the arguments
|
|
|
|
|
onto the lua stack, call the function, then pop the return
|
|
|
|
|
values from the lua stack. I find this difficult to use: I
|
|
|
|
|
find it hard to remember what's on the stack at any given
|
|
|
|
|
time, and which stack position contains which value. It's
|
|
|
|
|
not how I'm used to writing code.
|
|
|
|
|
|
|
|
|
|
So instead, we created our own in-house lua API called
|
|
|
|
|
"LuaStack." It allows you to create C++ local variables that
|
|
|
|
|
contain lua values. It has most of the same operations as
|
|
|
|
|
the original lua API, but they use different calling
|
|
|
|
|
conventions: you don't push arguments, and you don't pop
|
|
|
|
|
return values. Instead, you pass arguments as readable
|
|
|
|
|
parameters, and you pass return values as writable
|
|
|
|
|
parameters.
|
|
|
|
|
|
|
|
|
|
There's a caveat: we don't actually store lua values in C++
|
|
|
|
|
local variables. That would make it difficult for the lua
|
|
|
|
|
garbage collector to trace values. Instead, we use a hidden
|
|
|
|
|
level of indirection: the C++ local variable contains the
|
|
|
|
|
index of a pre-allocated spot on the lua stack. The
|
|
|
|
|
pre-allocated spot on the lua stack actually contains the
|
|
|
|
|
lua data. We'll walk you through the details of exactly how
|
|
|
|
|
this works in the following sections.
|
|
|
|
|
|
|
|
|
|
In our codebase, you are expected to use LuaStack, not
|
|
|
|
|
the original API, whenever possible.
|
|
|
|
|
|
|
|
|
|
== An Example of our API
|
|
|
|
|
|
2026-02-22 00:26:22 -05:00
|
|
|
This paper will explain LuaStack using the following example
|
|
|
|
|
function in Lua, which compares two tables to see if they
|
|
|
|
|
are exactly equal:
|
2026-02-05 12:40:27 -05:00
|
|
|
|
|
|
|
|
```lua
|
|
|
|
|
function table.equal(table1, table2)
|
2026-02-22 00:26:22 -05:00
|
|
|
if (type(table1) ~= "table") then
|
|
|
|
|
error("table1 must be a table")
|
|
|
|
|
end
|
|
|
|
|
if (type(table2) ~= "table") then
|
|
|
|
|
error("table2 must be a table")
|
|
|
|
|
end
|
|
|
|
|
local size1 = table.nkeys(table1)
|
|
|
|
|
local size2 = table.nkeys(table2)
|
|
|
|
|
if (size1 ~= size2) then
|
|
|
|
|
return false
|
|
|
|
|
end
|
|
|
|
|
for key, value1 in pairs(table1) do
|
|
|
|
|
local value2 = table2[key]
|
|
|
|
|
if value1 ~= value2 then
|
|
|
|
|
return false
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
return true
|
2026-02-05 12:40:27 -05:00
|
|
|
end
|
|
|
|
|
```
|
|
|
|
|
|
2026-02-22 00:26:22 -05:00
|
|
|
A few notes about that function. You can't just use the
|
|
|
|
|
expression "table1==table2." That would tell you if table1
|
|
|
|
|
and table2 are the same table (ie, pointer equality). The
|
|
|
|
|
function above, by contrast, checks if two different tables
|
|
|
|
|
contain the same key-value pairs. The built-in function
|
|
|
|
|
*table.nkeys* is something that we added to lua, it returns
|
2026-02-22 02:12:28 -05:00
|
|
|
the number of key-value pairs in a table, it works on any
|
|
|
|
|
table. Once you fully understand the Lua code above, have a
|
|
|
|
|
look at the following C++ code, which does the exact same
|
|
|
|
|
thing. Don't worry if you don't understand everything yet:
|
2026-02-05 12:40:27 -05:00
|
|
|
|
|
|
|
|
```cpp
|
2026-02-22 02:12:28 -05:00
|
|
|
LuaDefine(table_equal, "table1, table2",
|
|
|
|
|
"|Return true if two tables are equal."
|
|
|
|
|
"|"
|
|
|
|
|
"|The values in the table are not deep-compared,"
|
|
|
|
|
"|they are compared using pointer comparison."
|
|
|
|
|
) {
|
2026-02-22 00:26:22 -05:00
|
|
|
LuaArg table1, table2;
|
|
|
|
|
LuaVar size1, size2, key, value1, value2;
|
|
|
|
|
LuaRet equalflag;
|
|
|
|
|
LuaDefStack LS(L, table1, table2, size1, size2, key, value1, value2, equalflag);
|
|
|
|
|
LS.cktable(table1);
|
|
|
|
|
LS.cktable(table2);
|
|
|
|
|
LS.set(size1, LS.nkeys(table1));
|
|
|
|
|
LS.set(size2, LS.nkeys(table2));
|
|
|
|
|
if (!LS.rawequal(size1, size2)) {
|
|
|
|
|
LS.set(equalflag, false);
|
|
|
|
|
return LS.result();
|
|
|
|
|
}
|
|
|
|
|
LS.set(key, LuaNil);
|
|
|
|
|
while (LS.next(table1, key, value1)) {
|
|
|
|
|
LS.rawget(value2, table2, key);
|
|
|
|
|
if (!LS.rawequal(value1, value2)) {
|
|
|
|
|
LS.set(equalflag, false);
|
|
|
|
|
return LS.result();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
LS.set(equalflag, true);
|
|
|
|
|
return LS.result();
|
2026-02-05 12:40:27 -05:00
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2026-02-22 00:26:22 -05:00
|
|
|
There's a lot to unpack there. You might be wondering what
|
|
|
|
|
*LuaDefine* is, you might be wondering what *LS* is, you
|
|
|
|
|
might be wondering what the *LuaDefStack* constructor does,
|
|
|
|
|
and so forth. The rest of this paper is going to pick that
|
|
|
|
|
C++ function apart piece by piece. We're going to explain
|
|
|
|
|
every class, macro, and template used above. Once you're
|
|
|
|
|
done reading this paper, you'll understand at a deep level
|
|
|
|
|
how everything works.
|
2026-02-05 12:40:27 -05:00
|
|
|
|
|
|
|
|
## Class LuaArg, Class LuaVar, and Class LuaRet
|
|
|
|
|
|
2026-02-22 00:26:22 -05:00
|
|
|
In C++ example function, the first few lines inside the body
|
|
|
|
|
of the LuaDefine are:
|
2026-02-05 12:40:27 -05:00
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
|
LuaArg table1, table2;
|
|
|
|
|
LuaVar size1, size2, key, value1, value2;
|
|
|
|
|
LuaRet equalflag;
|
|
|
|
|
```
|
|
|
|
|
|
2026-02-22 00:26:22 -05:00
|
|
|
These are variable declarations. You are meant to think of
|
|
|
|
|
these as variables that can hold any lua data type - lua
|
|
|
|
|
tables, lua strings, lua numbers, lua closures, lua threads,
|
|
|
|
|
any data that the lua interpreter can handle. LuaArg are
|
|
|
|
|
function arguments, LuaVar are local variables, and LuaRet
|
|
|
|
|
are return values.
|
2026-02-05 12:40:27 -05:00
|
|
|
|
2026-02-22 00:26:22 -05:00
|
|
|
That sounds straightforward, but there's a catch. The Lua
|
|
|
|
|
garbage collector can't allow you to put Lua values into C++
|
|
|
|
|
variables. The reason for this is that if you were to put a
|
|
|
|
|
pointer to a Lua table into a C++ local variable, the Lua
|
|
|
|
|
garbage collector would not know that this pointer exists.
|
|
|
|
|
It might think there are no pointers to the table, and it
|
|
|
|
|
might free the table. In reality, we can't actually put Lua
|
|
|
|
|
values into C++ variables.
|
2026-02-05 12:40:27 -05:00
|
|
|
|
2026-02-22 00:26:22 -05:00
|
|
|
So that raises the question, how is it possible to have
|
|
|
|
|
LuaArg, LuaRet, and LuaVar variables that seemingly hold lua
|
|
|
|
|
values? The answer is that there's a hidden level of
|
|
|
|
|
indirection. The values are actually stored on the Lua
|
|
|
|
|
stack. In the example function, our variables end up on the
|
|
|
|
|
lua stack in the following order:
|
2026-02-05 12:40:27 -05:00
|
|
|
|
|
|
|
|
```
|
|
|
|
|
stack[1] holds equalflag
|
|
|
|
|
stack[2] holds size1
|
|
|
|
|
stack[3] holds size2
|
|
|
|
|
stack[4] holds key
|
|
|
|
|
stack[5] holds value1
|
|
|
|
|
stack[6] holds value2
|
|
|
|
|
stack[7] holds table1
|
|
|
|
|
stack[8] holds table2
|
|
|
|
|
```
|
|
|
|
|
|
2026-02-22 00:26:22 -05:00
|
|
|
In our example function, those eight stack positions are
|
|
|
|
|
permanently reserved to hold the values of those eight
|
|
|
|
|
variables. We never pop those values off the stack - at
|
|
|
|
|
least, not until the end of the lua function.
|
2026-02-05 12:40:27 -05:00
|
|
|
|
2026-02-22 00:26:22 -05:00
|
|
|
Here are the C++ declarations of class LuaArg, class LuaRet,
|
|
|
|
|
and class LuaVar, with methods omitted:
|
2026-02-05 12:40:27 -05:00
|
|
|
|
|
|
|
|
```cpp
|
2026-02-22 00:26:22 -05:00
|
|
|
class LuaSlot { int index_; };
|
2026-02-05 12:40:27 -05:00
|
|
|
class LuaArg : public LuaSlot {};
|
|
|
|
|
class LuaVar : public LuaSlot {};
|
|
|
|
|
class LuaRet : public LuaSlot {};
|
|
|
|
|
```
|
|
|
|
|
|
2026-02-22 00:26:22 -05:00
|
|
|
So as you can see, LuaArg, LuaRet, and LuaVar are all
|
|
|
|
|
derived from LuaSlot. It really is true that LuaArg, LuaRet,
|
|
|
|
|
and LuaVar don't add anything at all to class LuaSlot: their
|
|
|
|
|
class bodies literally are completely empty, as shown above.
|
|
|
|
|
A LuaSlot contains nothing but an integer index_.
|
2026-02-05 12:40:27 -05:00
|
|
|
|
2026-02-22 00:26:22 -05:00
|
|
|
In our example function, once the LuaSlots are initialized,
|
|
|
|
|
the index_ values are as follows:
|
2026-02-05 12:40:27 -05:00
|
|
|
|
|
|
|
|
```
|
2026-02-22 00:26:22 -05:00
|
|
|
equalflag.index_ == 1
|
|
|
|
|
size1.index_ == 2
|
|
|
|
|
size2.index_ == 3
|
|
|
|
|
key.index_ == 4
|
|
|
|
|
value1.index_ == 5
|
|
|
|
|
value2.index_ == 6
|
|
|
|
|
table1.index_ == 7
|
|
|
|
|
table2.index_ == 8
|
2026-02-05 12:40:27 -05:00
|
|
|
```
|
|
|
|
|
|
2026-02-22 00:26:22 -05:00
|
|
|
Technically, it is not exactly correct to say that the
|
|
|
|
|
LuaSlots "contain" lua values. It is more accurate to say
|
|
|
|
|
that the LuaSlots contain stack positions, and those stack
|
|
|
|
|
positions contain lua values. That is, there's a level of
|
|
|
|
|
indirection. However, when you're actually using the
|
|
|
|
|
LuaStack API, you don't need to think about that hidden
|
|
|
|
|
indirection. Conceptually, you're meant to think of the
|
|
|
|
|
LuaSlots as effectively containing lua values. For the
|
|
|
|
|
remainder of this paper, I will often say that LuaSlots
|
|
|
|
|
"contain" lua values, glossing over the existence of a level
|
|
|
|
|
of indirection.
|
2026-02-05 12:40:27 -05:00
|
|
|
|
|
|
|
|
## The LuaDefStack Constructor
|
|
|
|
|
|
2026-02-22 00:26:22 -05:00
|
|
|
In the previous section, I mentioned that the LuaSlots are
|
|
|
|
|
all assigned unique stack positions. You might be wondering:
|
|
|
|
|
where is the code that assigns unique stack positions to the
|
|
|
|
|
LuaSlots?
|
2026-02-05 12:40:27 -05:00
|
|
|
|
2026-02-22 00:26:22 -05:00
|
|
|
It's not in the LuaSlot constructor. The LuaSlot constructor
|
|
|
|
|
doesn't do anything but set index_ to 0. Since
|
|
|
|
|
everything in Lua is numbered from 1, that's an invalid
|
|
|
|
|
stack position. It means that the LuaSlot is still not
|
|
|
|
|
initialized. Any attempt to use the LuaSlot at this stage
|
|
|
|
|
will generate an error.
|
2026-02-05 12:40:27 -05:00
|
|
|
|
2026-02-22 00:26:22 -05:00
|
|
|
The machinery to assign unique stack positions lives inside
|
|
|
|
|
the LuaDefStack constructor. LuaSlots are useless without a
|
|
|
|
|
LuaDefStack to set them up. This section will describe the
|
|
|
|
|
LuaDefStack constructor, and all the various functions it
|
|
|
|
|
performs.
|
2026-02-05 12:40:27 -05:00
|
|
|
|
2026-02-22 00:26:22 -05:00
|
|
|
In our example C++ function, you'll find the LuaDefStack
|
|
|
|
|
constructor immediately after the LuaSlot declarations:
|
2026-02-05 12:40:27 -05:00
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
|
LuaDefStack LS(L, table1, table2, size1, size2, key, value1, value2, equalflag);
|
|
|
|
|
```
|
|
|
|
|
|
2026-02-22 00:26:22 -05:00
|
|
|
As you can see, the first argument to the LuaDefStack
|
|
|
|
|
constructor is a capital L. That's boilerplate: the first
|
|
|
|
|
argument is always capital L. The declaration of that
|
|
|
|
|
variable, *lua_State *L,* is concealed inside the
|
|
|
|
|
*LuaDefine* macro. I'll talk about LuaDefine later. That
|
|
|
|
|
data type, *lua_State*,* is from the standard Lua API. It's
|
|
|
|
|
a C-style pointer to the lua stack. The LuaDefStack
|
|
|
|
|
constructor stores this pointer inside the LuaDefStack.
|
|
|
|
|
|
|
|
|
|
After the capital L, all the LuaSlots have been passed to
|
|
|
|
|
the LuaDefStack constructor. They are passed by non-const
|
|
|
|
|
reference, which means that the LuaDefStack can mutate them.
|
|
|
|
|
|
|
|
|
|
The LuaDefStack constructor allocates stack space for the
|
|
|
|
|
LuaSlots. In our example function, it counts that there are
|
|
|
|
|
eight LuaSlots, so it allocates the first eight positions on
|
|
|
|
|
the lua stack to hold them. Then, it chooses a stack
|
|
|
|
|
position in the range 1 to 8 for each of the LuaSlot
|
|
|
|
|
variables. It mutates each LuaSlot to contain the correct
|
|
|
|
|
index_.
|
|
|
|
|
|
|
|
|
|
The LuaDefStack constructor notices that two of the LuaSlots
|
|
|
|
|
are of class LuaArg. It knows that LuaArgs are meant to hold
|
|
|
|
|
function arguments. So it checks that two function arguments
|
|
|
|
|
were passed to the function. If that's not the case, it
|
|
|
|
|
throws a lua error. But if all is well, it moves the
|
|
|
|
|
function arguments into the stack positions reserved for the
|
|
|
|
|
LuaArgs.
|
|
|
|
|
|
|
|
|
|
This is why we have the declaration of LuaArg which derives
|
|
|
|
|
from LuaSlot, but doesn't add any functionality to class
|
|
|
|
|
LuaSlot. Even though LuaArg inherits everything from
|
|
|
|
|
LuaSlot, the LuaDefStack constructor can differentiate it,
|
|
|
|
|
and treat it as special.
|
|
|
|
|
|
|
|
|
|
The LuaDefStack initializes the contents of all the
|
|
|
|
|
LuaSlots. As mentioned above, the LuaArg are initialized
|
|
|
|
|
from the function arguments. The LuaRet and LuaVar are
|
|
|
|
|
initialized with nil. After the LuaDefStack constructor, all
|
|
|
|
|
the LuaSlots have the correct index_ values, and via that
|
|
|
|
|
level of indirection, the LuaSlots all contain the correct
|
|
|
|
|
lua values.
|
|
|
|
|
|
|
|
|
|
Meanwhile, the LuaDefStack contains the pointer to the lua
|
|
|
|
|
stack. Note that in the example function, the object of
|
|
|
|
|
class LuaDefStack is named *LS*. That stands for "Lua
|
|
|
|
|
Stack." That is a naming convention that we use throughout
|
|
|
|
|
our codebase - the LuaDefStack is called *LS*. But unlike
|
|
|
|
|
*lua_State* L*, which is also a pointer to the lua stack,
|
|
|
|
|
*LuaDefStack LS* is a C++ style smart pointer, which has
|
|
|
|
|
methods.
|
2026-02-05 12:40:27 -05:00
|
|
|
|
2026-02-22 02:12:28 -05:00
|
|
|
When the function returns, the only thing
|
|
|
|
|
left on the stack is supposed to be the return values.
|
|
|
|
|
The LuaDefStack is responsible for making that happen.
|
|
|
|
|
In the function LS.return, the stack is rearranged
|
|
|
|
|
to put the return values on the stack and remove
|
|
|
|
|
everything else. More on that later.
|
|
|
|
|
|
2026-02-05 12:40:27 -05:00
|
|
|
## Methods of Class LuaDefStack
|
|
|
|
|
|
|
|
|
|
In our example function, you'll find this line of code:
|
|
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
|
LS.set(size1, LS.nkeys(table1));
|
|
|
|
|
```
|
|
|
|
|
|
2026-02-22 00:26:22 -05:00
|
|
|
Let's break that down into pieces. Here's a simpler
|
|
|
|
|
statement, using just LS.set:
|
2026-02-05 12:40:27 -05:00
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
|
LS.set(size1, 3);
|
|
|
|
|
```
|
|
|
|
|
|
2026-02-22 00:26:22 -05:00
|
|
|
Conceptually, you're supposed to think of that in the very
|
|
|
|
|
obvious way: it sets *size1* to 3. But you also know that
|
|
|
|
|
there's a hidden level of indirection, so you might be
|
|
|
|
|
wondering about *how* it sets *size1* to 3.
|
2026-02-05 12:40:27 -05:00
|
|
|
|
|
|
|
|
The function LS.set is a call to the following method:
|
|
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
|
void LuaDefStack::set(const LuaSlot &slot, int value) const;
|
|
|
|
|
```
|
|
|
|
|
|
2026-02-22 00:26:22 -05:00
|
|
|
Inside that method, *this* contains the pointer to the lua
|
|
|
|
|
stack, *slot* contains the necessary index_, and
|
|
|
|
|
*value* contains the value to store. What this method
|
|
|
|
|
actually does is set stack[index_] = value. In our
|
|
|
|
|
example function we know that *size1.index_* == 2,
|
|
|
|
|
and *value* == 3. So this sets stack[2] = 3. But again,
|
|
|
|
|
you're meant to ignore the hidden indirection, you're
|
|
|
|
|
supposed to think of it as just setting *size1* to 3.
|
2026-02-05 12:40:27 -05:00
|
|
|
|
2026-02-22 00:26:22 -05:00
|
|
|
OK, now, let's consider this statement from the example
|
|
|
|
|
function:
|
2026-02-05 12:40:27 -05:00
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
|
LS.set(size1, LS.nkeys(table1));
|
|
|
|
|
```
|
|
|
|
|
|
2026-02-22 00:26:22 -05:00
|
|
|
That's two method calls. The *nkeys* method call fetches the
|
|
|
|
|
size of *table1*, returning it as an 'int'. The *set* method
|
|
|
|
|
stores the int in the appropriate stack slot for *size1*.
|
2026-02-05 12:40:27 -05:00
|
|
|
|
2026-02-22 00:26:22 -05:00
|
|
|
The first argument to LuaDefStack::set must be a LuaSlot.
|
|
|
|
|
The second argument is overloaded, it can be any one of
|
|
|
|
|
several different data types:
|
2026-02-05 12:40:27 -05:00
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
|
LS.set(slota, "hello"); // set to a char*
|
|
|
|
|
LS.set(slota, 3.14159); // set to a double
|
|
|
|
|
LS.set(slota, true); // set to a bool
|
|
|
|
|
LS.set(slota, LuaNil); // set to nil
|
|
|
|
|
LS.set(slota, slotb); // copy from another LuaSlot
|
|
|
|
|
```
|
|
|
|
|
|
2026-02-22 00:26:22 -05:00
|
|
|
In general, methods of LuaDefStack will automatically
|
|
|
|
|
convert the following C++ types to Lua: int, int64_t, float,
|
|
|
|
|
double, char *, string, string_view, and bool. If you want
|
|
|
|
|
to convert lua values back to C++ values, it's a little
|
|
|
|
|
harder. More on that in the next section.
|
2026-02-05 12:40:27 -05:00
|
|
|
|
2026-02-22 00:26:22 -05:00
|
|
|
For a complete listing of all the operators in LuaDefStack,
|
|
|
|
|
have a look at the header file luastack.hpp, at the class
|
|
|
|
|
LuaCoreStack. There, you will find prototypes for all the
|
|
|
|
|
functions, along with documentation for each one.
|
2026-02-05 12:40:27 -05:00
|
|
|
|
|
|
|
|
## Converting Lua Values to C++ Values
|
|
|
|
|
|
2026-02-22 00:26:22 -05:00
|
|
|
Suppose that you have a function, callable from lua, that is
|
|
|
|
|
supposed to accept a string as a parameter:
|
2026-02-05 12:40:27 -05:00
|
|
|
|
|
|
|
|
```cpp
|
2026-02-22 00:26:22 -05:00
|
|
|
LuaDefine(frob, "str", "Frobnicate a string.") {
|
|
|
|
|
LuaArg str;
|
|
|
|
|
LuaDefStack LS(L, str);
|
|
|
|
|
…etc…
|
2026-02-05 12:40:27 -05:00
|
|
|
```
|
|
|
|
|
|
2026-02-22 00:26:22 -05:00
|
|
|
The variable 'str' is a LuaSlot. Suppose you want to convert
|
|
|
|
|
that to a C++ string. LuaDefStack provides a method that can
|
|
|
|
|
do that:
|
2026-02-05 12:40:27 -05:00
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
|
eng::string s = LS.ckstring(str);
|
|
|
|
|
```
|
|
|
|
|
|
2026-02-22 00:26:22 -05:00
|
|
|
The method *ckstring* verifies that *str* really is a lua
|
|
|
|
|
string. If it is a string, it converts it to an eng::string
|
|
|
|
|
and returns it. If it's not a string, ckstring throws a lua
|
|
|
|
|
error: "value must be a string". That's not an especially
|
|
|
|
|
good error message, it lacks all context. You can make it
|
|
|
|
|
slightly better, "str must be a string", by passing the
|
|
|
|
|
variable's name to ckstring:
|
2026-02-05 12:40:27 -05:00
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
|
std::string s = LS.ckstring(str, "str");
|
|
|
|
|
```
|
|
|
|
|
|
2026-02-22 00:26:22 -05:00
|
|
|
Class LuaDefStack provides a full catalog of conversion
|
|
|
|
|
functions that return different C++ data types:
|
2026-02-05 12:40:27 -05:00
|
|
|
|
|
|
|
|
```cpp
|
2026-02-22 00:26:22 -05:00
|
|
|
bool ckboolean(LuaSlot s, const char *argname = "value") const;
|
|
|
|
|
lua_Integer ckinteger(LuaSlot s, const char *argname = "value") const;
|
|
|
|
|
int ckint(LuaSlot s, const char *argname = "value") const;
|
|
|
|
|
lua_Number cknumber(LuaSlot s, const char *argname = "value") const;
|
|
|
|
|
eng::string ckstring(LuaSlot s, const char *argname = "value") const;
|
2026-02-05 12:40:27 -05:00
|
|
|
std::string_view ckstringview(LuaSlot s, const char *argname = "value") const;
|
2026-02-22 00:26:22 -05:00
|
|
|
lua_State * ckthread(LuaSlot s, const char *argname = "value") const;
|
|
|
|
|
LuaToken cktoken(LuaSlot s, const char *argname = "value") const;
|
|
|
|
|
util::DXYZ ckxyz(LuaSlot s, const char *argname = "value") const;
|
|
|
|
|
void cktable(LuaSlot s, const char *argname = "value") const;
|
|
|
|
|
void cknil(LuaSlot s, const char *argname = "value") const;
|
|
|
|
|
void ckfunction(LuaSlot s, const char *argname = "value") const;
|
|
|
|
|
void ckcfunction(LuaSlot s, const char *argname = "value") const;
|
|
|
|
|
void cktangible(LuaSlot s, const char *argname = "value") const;
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Notice that cktable returns void. That's because you can't
|
|
|
|
|
convert a lua table to a C++ anything. So cktable just
|
|
|
|
|
checks the type and if it's not a table, it throws a lua
|
|
|
|
|
error, "value must be a table". If it is a table, cktable
|
|
|
|
|
doesn't do anything at all. Note that we used *LS.cktable*
|
|
|
|
|
in the example table_equal function, to verify that the
|
|
|
|
|
caller passed in two tables.
|
|
|
|
|
|
|
|
|
|
OK, so what if you want to convert the LuaSlot str to a C++
|
|
|
|
|
string, but you want to handle the non-string exception case
|
|
|
|
|
yourself? Use this code:
|
2026-02-05 12:40:27 -05:00
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
|
std::optional<eng::string> s = LS.trystring(str);
|
|
|
|
|
```
|
|
|
|
|
|
2026-02-22 00:26:22 -05:00
|
|
|
You may not be familiar with C++ std::optional, so I'll
|
|
|
|
|
summarize here. An optional<string> can either contain a
|
|
|
|
|
string, or it can contain nothing. There are methods that
|
|
|
|
|
can check if the optional is empty, and methods to extract
|
|
|
|
|
the string value, if present.
|
2026-02-05 12:40:27 -05:00
|
|
|
|
2026-02-22 00:26:22 -05:00
|
|
|
What LS.trystring(str) does is: if str is a string,
|
|
|
|
|
trystring returns an optional<string> containing the string.
|
|
|
|
|
If str is not a string, trystring returns an
|
|
|
|
|
optional<string> that is empty.
|
2026-02-05 12:40:27 -05:00
|
|
|
|
2026-02-22 00:26:22 -05:00
|
|
|
Unlike the ck-functions functions which throw errors on type
|
|
|
|
|
mismatch, the try-functions never throw an error. Using the
|
|
|
|
|
try-functions is ideal in code where throwing errors would
|
|
|
|
|
be bad.
|
2026-02-05 12:40:27 -05:00
|
|
|
|
2026-02-22 00:26:22 -05:00
|
|
|
What if you just want to know if it's a string, but you
|
|
|
|
|
don't need the actual string? In that case, use *isstring:*
|
2026-02-05 12:40:27 -05:00
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
|
bool b = LS.isstring(str);
|
|
|
|
|
```
|
|
|
|
|
|
2026-02-22 00:26:22 -05:00
|
|
|
You can also use LS.type to fetch the type of a lua value as
|
|
|
|
|
an enum.
|
2026-02-05 12:40:27 -05:00
|
|
|
|
|
|
|
|
## Dealing with Return Values
|
|
|
|
|
|
|
|
|
|
Our sample function contains this code:
|
|
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
|
LuaRet equalflag;
|
|
|
|
|
…
|
2026-02-22 00:26:22 -05:00
|
|
|
if (!LS.rawequal(size1, size2)) {
|
|
|
|
|
LS.set(equalflag, false);
|
|
|
|
|
return LS.result();
|
2026-02-05 12:40:27 -05:00
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2026-02-22 00:26:22 -05:00
|
|
|
The variable declaration, "LuaRet equalflag", declares that
|
|
|
|
|
this function has a return value whose name is equalflag. In
|
|
|
|
|
our API, return values are just variables. You have to
|
|
|
|
|
assign a value to the variable before you return from the
|
|
|
|
|
function. In the code above, when size1 != size2, it stores
|
|
|
|
|
*false* into equalflag. Then, there is a return-statement.
|
2026-02-05 12:40:27 -05:00
|
|
|
|
2026-02-22 00:26:22 -05:00
|
|
|
Lua calling conventions dictate that at the end of a
|
|
|
|
|
function, there shouldn't be anything on the lua stack
|
|
|
|
|
except for the return values. That is accomplished by the
|
|
|
|
|
method LS.result. This takes everything off the stack except
|
|
|
|
|
for the LuaRet values. Another lua calling convention is
|
|
|
|
|
that the function must return the number of return values
|
|
|
|
|
that are on the lua stack. Therefore, LS.result returns the
|
|
|
|
|
number of LuaRet values that were declared.
|
2026-02-05 12:40:27 -05:00
|
|
|
|
2026-02-22 00:26:22 -05:00
|
|
|
So, in every LuaDefine, the process of returning from a
|
|
|
|
|
function always consists of two steps:
|
2026-02-05 12:40:27 -05:00
|
|
|
|
2026-02-22 00:26:22 -05:00
|
|
|
1. You must set the LuaRet slots to whatever values you want
|
|
|
|
|
to return.
|
|
|
|
|
2. There must be an explicit return-statement that must
|
|
|
|
|
always be *return LS.result()*
|
2026-02-05 12:40:27 -05:00
|
|
|
|
|
|
|
|
## The LuaDefine Macro
|
|
|
|
|
|
2026-02-22 02:12:28 -05:00
|
|
|
Our sample function starts with this:
|
2026-02-05 12:40:27 -05:00
|
|
|
|
|
|
|
|
```cpp
|
2026-02-22 02:12:28 -05:00
|
|
|
LuaDefine(table_equal, "table1, table2",
|
|
|
|
|
"|Return true if two tables are equal."
|
|
|
|
|
"|"
|
|
|
|
|
"|The values in the table are not deep-compared,"
|
|
|
|
|
"|they are compared using pointer comparison."
|
|
|
|
|
)
|
|
|
|
|
{ ... function body here ...}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
LuaDefine is a C macro that takes three parameters: the
|
|
|
|
|
function name, the parameter list docstring, and the big
|
|
|
|
|
docstring. Note that in C++, when you put two strings right
|
|
|
|
|
next to each other, C++ automatically concatenates them into
|
|
|
|
|
one string, and we've used that in the big docstring above.
|
|
|
|
|
|
|
|
|
|
The two strings passed to the LuaDefine are just
|
|
|
|
|
documentation. Neither has any effect on how the function
|
|
|
|
|
works. They are collected automatically by our documentation
|
|
|
|
|
system, which can print them out on demand.
|
|
|
|
|
|
|
|
|
|
The big docstring doesn't contain newlines. Instead, we use
|
|
|
|
|
the pipe character to designate where new lines should
|
|
|
|
|
start. I find it harder to screw up.
|
|
|
|
|
|
|
|
|
|
Before I tell you what LuaDefine macro expands into, I'd
|
|
|
|
|
like to tell you how you write C functions that are intended
|
|
|
|
|
to be inserted into Lua:
|
2026-02-05 12:40:27 -05:00
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
|
int table_equal(lua_State *L) {
|
2026-02-22 00:26:22 -05:00
|
|
|
…
|
2026-02-05 12:40:27 -05:00
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2026-02-22 00:26:22 -05:00
|
|
|
The C prototype is always the same: the return value is
|
2026-02-22 02:12:28 -05:00
|
|
|
always *int,* and the only argument is *lua_State *L*, a
|
2026-02-22 00:26:22 -05:00
|
|
|
pointer to the lua stack. A C function must have this
|
|
|
|
|
prototype to be compatible with the lua runtime.
|
2026-02-05 12:40:27 -05:00
|
|
|
|
2026-02-22 00:26:22 -05:00
|
|
|
Next, I will show you the macro expansion of a LuaDefine to
|
2026-02-22 02:12:28 -05:00
|
|
|
help you understand what it does. Here is the macroexpansion
|
|
|
|
|
of the LuaDefine above:
|
2026-02-05 12:40:27 -05:00
|
|
|
|
|
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
|
int lfn_table_equal(lua_State *L);
|
2026-02-22 00:26:22 -05:00
|
|
|
const char *lfnarg_table_equal = "table1, table2";
|
2026-02-22 02:12:28 -05:00
|
|
|
const char *lfndoc_table_equal =
|
|
|
|
|
"|Return true if two tables are equal."
|
|
|
|
|
"|"
|
|
|
|
|
"|The values in the table are not deep-compared,"
|
|
|
|
|
"|they are compared using pointer comparison.";
|
2026-02-22 00:26:22 -05:00
|
|
|
LuaFunctionReg reg_table_equal(
|
|
|
|
|
"table_equal", lfnarg_table_equal, lfndoc_table_equal, false, lfn_table_equal);
|
2026-02-05 12:40:27 -05:00
|
|
|
int lfn_table_equal(lua_State *L)
|
|
|
|
|
```
|
|
|
|
|
|
2026-02-22 00:26:22 -05:00
|
|
|
So first, you can see that there's a prototype for the
|
|
|
|
|
function lfn_table_equal. As you can see, it returns *int*
|
|
|
|
|
and accepts *lua_State *L,* which makes it compatible with
|
|
|
|
|
the lua runtime. This is a forward declaration.
|
|
|
|
|
|
2026-02-22 02:12:28 -05:00
|
|
|
Next, there are the two documentation strings.
|
|
|
|
|
|
|
|
|
|
Next, class LuaFunctionReg implements a registry of
|
|
|
|
|
LuaDefines. It is the means by which we can loop over all
|
|
|
|
|
LuaDefines and insert them into a lua environment. The
|
|
|
|
|
constructor of LuaFunctionReg runs at program initialization
|
|
|
|
|
time. The constructor stores its arguments: the function
|
|
|
|
|
name, the documentation strings, the flags, and the C
|
|
|
|
|
function pointer. These are all stored inside
|
|
|
|
|
reg_table_equal. Then, reg_table_equal is linked
|
|
|
|
|
into a global linked list of LuaFunctionReg objects. This
|
|
|
|
|
linked list makes up the registry of all LuaDefines.
|
2026-02-22 00:26:22 -05:00
|
|
|
|
|
|
|
|
Finally, we have a second copy of the prototype for
|
|
|
|
|
lfn_table_equal. This time, it's not a forward declaration:
|
|
|
|
|
there's no semicolon. This prototype is meant to be followed
|
2026-02-22 02:12:28 -05:00
|
|
|
by the function body. A LuaDefine must always be followed
|
|
|
|
|
by a function body.
|
2026-02-22 00:26:22 -05:00
|
|
|
|
|
|
|
|
The upshot of all this is that LuaDefine doesn't just
|
2026-02-22 02:12:28 -05:00
|
|
|
declare a function. It also adds that function to the
|
|
|
|
|
registry of all LuaDefines. The registry contains not only
|
|
|
|
|
the function name and function pointer, but also the
|
|
|
|
|
function documentation. Remember also that in C++, static
|
|
|
|
|
objects like reg_table_equal are constructed before *main*.
|
|
|
|
|
So by the time *main* starts, the registry is ready-to-use.
|
2026-02-05 12:40:27 -05:00
|
|
|
|
|
|
|
|
The registry of all LuaDefines has two primary uses:
|
|
|
|
|
|
2026-02-22 00:26:22 -05:00
|
|
|
1. The routine source_load_cfunctions traverses the registry
|
|
|
|
|
and inserts every LuaDefine into the lua environment.
|
|
|
|
|
It's fully automatic: simply writing a LuaDefine causes
|
|
|
|
|
the function to appear in Lua.
|
2026-02-05 12:40:27 -05:00
|
|
|
|
2026-02-22 00:26:22 -05:00
|
|
|
2. The documentation system traverses the registry to find
|
|
|
|
|
the documentation strings. It uses these strings to
|
|
|
|
|
auto-generate the manual.
|
2026-02-05 12:40:27 -05:00
|
|
|
|
|
|
|
|
## Class LuaExtStack
|
|
|
|
|
|
2026-02-22 00:26:22 -05:00
|
|
|
What if you want to write C++ code that manipulates Lua
|
|
|
|
|
data, but which *isn't* called from inside Lua? In that
|
|
|
|
|
case, LuaDefStack won't work, because LuaDefStack is
|
2026-02-22 02:12:28 -05:00
|
|
|
hardwired to assume that a lua caller *exists*,
|
|
|
|
|
and that it wants to pass arguments and return values:
|
2026-02-22 00:26:22 -05:00
|
|
|
|
|
|
|
|
- It checks that the caller passed the right number of
|
|
|
|
|
arguments.
|
|
|
|
|
|
|
|
|
|
- It transfers the arguments from the caller to the LuaArg
|
|
|
|
|
slots.
|
2026-02-22 02:12:28 -05:00
|
|
|
|
2026-02-22 00:26:22 -05:00
|
|
|
- It transfers the LuaRet slots back to the caller at
|
|
|
|
|
function end.
|
|
|
|
|
|
2026-02-22 02:12:28 -05:00
|
|
|
By contrast, class LuaExtStack is for a situation where
|
|
|
|
|
you're in the middle of doing some C++ processing, and
|
|
|
|
|
you suddenly decide you need to store some more lua
|
|
|
|
|
variables. Class LuaExtStack will allocate space for
|
|
|
|
|
the variables.
|
2026-02-22 00:26:22 -05:00
|
|
|
|
|
|
|
|
- LuaExtStack doesn't accept LuaArg or LuaRet parameters -
|
2026-02-22 02:12:28 -05:00
|
|
|
it only allows LuaVar parameters.
|
2026-02-22 00:26:22 -05:00
|
|
|
|
|
|
|
|
- There is no code in LuaExtStack to check that the caller
|
2026-02-22 02:12:28 -05:00
|
|
|
passed the right number of arguments, since again, it
|
|
|
|
|
is not meant to be used in that context.
|
|
|
|
|
|
2026-02-22 00:26:22 -05:00
|
|
|
- Class LuaExtStack doesn't have a method LS.result, since
|
|
|
|
|
there's nobody to return a result to.
|
|
|
|
|
|
|
|
|
|
- Class LuaDefStack assumes that anything already on the
|
|
|
|
|
stack must be a function argument. Class LuaExtStack
|
|
|
|
|
assumes that anything already on the stack is data that it
|
|
|
|
|
is not supposed to touch in any way: it leaves it alone.
|
|
|
|
|
It allocates stack space above whatever is already there.
|
|
|
|
|
|
|
|
|
|
- Class LuaExtStack has a destructor that automatically pops
|
|
|
|
|
the stack back to the level where it was when the
|
|
|
|
|
LuaExtStack was constructed. This is convenient.
|
|
|
|
|
LuaDefStack, on the other hand, doesn't restore the stack
|
|
|
|
|
top: it can't, because return values must be left on the
|
|
|
|
|
stack.
|
|
|
|
|
|
|
|
|
|
Aside from those differences, LuaExtStack and LuaDefStack
|
|
|
|
|
are quite similar. For example, all the methods are the
|
|
|
|
|
same: LS.set, LS.ckstring, LS.nkeys, and so forth all work
|
|
|
|
|
the same with LuaDefStack and LuaExtStack.
|
2026-02-05 12:40:27 -05:00
|
|
|
|
|
|
|
|
## Using Multiple Lua Stacks
|
|
|
|
|
|
2026-02-22 00:26:22 -05:00
|
|
|
In 95% of the code you'll write, there is only one lua
|
|
|
|
|
stack. However, there are two big exceptions to this rule.
|
2026-02-05 12:40:27 -05:00
|
|
|
|
2026-02-22 00:26:22 -05:00
|
|
|
In the difference-transmission code, you need to compare the
|
|
|
|
|
master world model to the synchronous world model. Each
|
|
|
|
|
world model has its own instance of the lua interpreter.
|
|
|
|
|
Each instance of the lua interpreter has its own lua stack.
|
|
|
|
|
So when comparing master to synchronous, you're manipulating
|
|
|
|
|
two lua stacks at the same time.
|
2026-02-05 12:40:27 -05:00
|
|
|
|
2026-02-22 00:26:22 -05:00
|
|
|
In that situation, it is typical to use two LuaExtStacks:
|
|
|
|
|
one for the master model, one for the synchronous model. The
|
|
|
|
|
naming convention is as follows:
|
2026-02-05 12:40:27 -05:00
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
|
LuaVar mtable; // A variable meant to hold a master model table.
|
|
|
|
|
LuaVar stable; // A variable meant to hold a synchronous model table.
|
|
|
|
|
LuaExtStack MLS(ML, mtable); // Master world model stack object
|
|
|
|
|
LuaExtStack SLS(SL, stable); // Synchronous world model stack object
|
|
|
|
|
```
|
|
|
|
|
|
2026-02-22 00:26:22 -05:00
|
|
|
In cases like that, it is essential to be careful - every
|
|
|
|
|
LuaSlot must be used with the correct stack. For example,
|
|
|
|
|
this will cause a crash:
|
2026-02-05 12:40:27 -05:00
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
|
MLS.set(stable, 3);
|
|
|
|
|
```
|
|
|
|
|
|
2026-02-22 00:26:22 -05:00
|
|
|
That's because stable contains an index_ on the SLS
|
|
|
|
|
stack, not an index_ on the MLS stack. Every LuaSlot
|
|
|
|
|
is associated with one and only one lua stack, and you must
|
|
|
|
|
pair them up correctly.
|
2026-02-05 12:40:27 -05:00
|
|
|
|
2026-02-22 00:26:22 -05:00
|
|
|
The other place we use two stacks is inside World::spawn.
|
|
|
|
|
This does some initial calculation using the default stack
|
|
|
|
|
of the world model. It creates a LuaExtStack to manipulate
|
|
|
|
|
the default stack. Then, after the initial calculations are
|
|
|
|
|
done, it actually creates the new thread, which has its own
|
|
|
|
|
stack. In the current codebase, the new thread doesn't get
|
|
|
|
|
its own LuaExtStack. Instead, it's simpler to just use the
|
|
|
|
|
standard API to set up the new thread and move the data onto
|
|
|
|
|
it. Therefore, we avoid the risk of using LuaSlot objects
|
|
|
|
|
with the wrong LuaExtStack.
|
2026-02-05 12:40:27 -05:00
|
|
|
|
|
|
|
|
## Mixing LuaStack with the Standard API
|
|
|
|
|
|
2026-02-22 00:26:22 -05:00
|
|
|
It is rarely necessary to call functions in the standard API
|
|
|
|
|
- the LuaStack API is meant to be a complete replacement.
|
|
|
|
|
But every now and then, you encounter a case that LuaStack
|
|
|
|
|
doesn't handle, or doesn't handle easily. In cases like
|
|
|
|
|
that, it's possible to mix the standard API with the
|
|
|
|
|
LuaStack API.
|
2026-02-05 12:40:27 -05:00
|
|
|
|
2026-02-22 00:26:22 -05:00
|
|
|
Here's an example. When you type some lua code into the
|
|
|
|
|
luprex console, that code has to be compiled into a closure,
|
|
|
|
|
after which the closure gets called. This is all handled by
|
|
|
|
|
the function World::invoke_lua.
|
2026-02-05 12:40:27 -05:00
|
|
|
|
2026-02-22 00:26:22 -05:00
|
|
|
Our LuaStack API does provide a method to compile lua code
|
|
|
|
|
into a closure (LS.load), but for historical reasons,
|
|
|
|
|
World::invoke_lua uses the standard lua API to compile the
|
|
|
|
|
code. This is an excerpt from World::invoke_lua:
|
2026-02-05 12:40:27 -05:00
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
|
LuaVar func;
|
|
|
|
|
LuaExtStack LS(L, func);
|
|
|
|
|
int status = luaL_loadbuffer(L, datapack.data(), datapack.size(), "=invoke");
|
2026-02-22 00:26:22 -05:00
|
|
|
lua_replace(L, func.index());
|
2026-02-05 12:40:27 -05:00
|
|
|
```
|
|
|
|
|
|
2026-02-22 00:26:22 -05:00
|
|
|
So the first two lines use the LuaStack API. We've defined a
|
|
|
|
|
LuaVar *func* to hold the compiled closure. The LuaExtStack
|
|
|
|
|
constructor allocates one space on the lua stack to hold
|
|
|
|
|
*func*. At this point, the lua stack contains:
|
2026-02-05 12:40:27 -05:00
|
|
|
|
|
|
|
|
```
|
|
|
|
|
stack[1] contains func, whose value is nil
|
|
|
|
|
```
|
|
|
|
|
|
2026-02-22 00:26:22 -05:00
|
|
|
Then, we call the function luaL_loadbuffer. This is a
|
|
|
|
|
function from the standard lua API, it compiles a source
|
|
|
|
|
code string and pushes the compiled closure on the stack.
|
|
|
|
|
The C++ variable "datapack" contains the lua source code
|
|
|
|
|
that was typed in at the console. After calling
|
|
|
|
|
luaL_loadbuffer, the stack contains:
|
2026-02-05 12:40:27 -05:00
|
|
|
|
|
|
|
|
```
|
|
|
|
|
stack[1] contains func, whose value is nil
|
|
|
|
|
stack[2] contains the compiled closure
|
|
|
|
|
```
|
|
|
|
|
|
2026-02-22 00:26:22 -05:00
|
|
|
Finally, our snippet calls lua_replace. This is a function
|
|
|
|
|
from the standard lua API, it pops the stack and stores the
|
|
|
|
|
popped value at a specified position on the lua stack. We
|
|
|
|
|
use this to pop the compiled closure off the stack, and
|
|
|
|
|
transfer it into the LuaVar *func*. After calling
|
|
|
|
|
lua_replace, the stack contains:
|
2026-02-05 12:40:27 -05:00
|
|
|
|
|
|
|
|
```
|
|
|
|
|
stack[1] contains func, whose value is the compiled closure
|
|
|
|
|
```
|
|
|
|
|
|
2026-02-22 00:26:22 -05:00
|
|
|
From this point forward, we are done using the standard API.
|
|
|
|
|
The rest of World::invoke_lua uses only the LuaStack API. So
|
|
|
|
|
as you see, it is often possible to write most of the code
|
|
|
|
|
using the LuaStack API, switching to the standard API for
|
|
|
|
|
only two or three lines of code.
|
2026-02-05 12:40:27 -05:00
|
|
|
|
2026-02-22 00:26:22 -05:00
|
|
|
The standard API generally manipulates values that are on
|
|
|
|
|
top of the stack. Generally, when you're done using the
|
|
|
|
|
standard API, you want to move the values from the top of
|
|
|
|
|
the stack to a LuaSlot. As demonstrated above, that is done
|
|
|
|
|
using lua_replace.
|
2026-02-05 12:40:27 -05:00
|
|
|
|
2026-02-22 00:26:22 -05:00
|
|
|
There's a mirror image of this function: lua_pushvalue. This
|
|
|
|
|
is useful for copying a value from a LuaSlot to the top of
|
|
|
|
|
the stack. This is generally useful when you want to take a
|
|
|
|
|
value from a LuaSlot, and then manipulate it using the
|
|
|
|
|
standard API.
|
2026-02-22 02:12:28 -05:00
|
|
|
|
|
|
|
|
## Metamethod Functions missing from our API
|
|
|
|
|
|
|
|
|
|
Several API functions from the old Lua API will
|
|
|
|
|
automatically call metamethods. For example, consider the
|
|
|
|
|
function lua_less. This can be used to compare two
|
|
|
|
|
strings. But if you store the right metamethod, lua_less
|
|
|
|
|
can end up calling a closure and running lua code.
|
|
|
|
|
|
|
|
|
|
I feel strongly that this is a *very dangerous* design
|
|
|
|
|
decision. Imagine that you're writing code for the
|
|
|
|
|
difference transmitter. You *think* you're just comparing
|
|
|
|
|
two strings, but all of a sudden you're running untrusted
|
|
|
|
|
lua code. If that code generates a lua error, suddenly
|
|
|
|
|
you're throwing a C++ exception from inside the difference
|
|
|
|
|
transmitter.
|
|
|
|
|
|
|
|
|
|
Because of this, LuaStack is *strongly* discouraging the use of
|
|
|
|
|
lua API functions that use metamethods: they're not even
|
|
|
|
|
exposed through the LuaStack API. Here is a short list
|
|
|
|
|
of API functions that use metamethods and substitutes that
|
|
|
|
|
don't:
|
|
|
|
|
|
|
|
|
|
| Uses Metamethods | Safe Substitute |
|
|
|
|
|
|-------------------------|--------------------------|
|
|
|
|
|
| lua_gettable | LS.rawget |
|
|
|
|
|
| lua_settable | LS.rawset |
|
|
|
|
|
| lua_equal | LS.rawequal |
|
|
|
|
|
| lua_lessthan | LS.genlt |
|
|
|
|
|
| lua_len | LS.rawlen or LS.nkeys |
|
|
|
|
|
|
|
|
|
|
## Our Extensions to Lua
|
|
|
|
|
|
|
|
|
|
We have made many extensions to the lua interpreter:
|
|
|
|
|
|
|
|
|
|
* We have created a new lua datatype, the "token". This is
|
|
|
|
|
a string-like datatype, limited to holding short lua
|
|
|
|
|
identifiers. The entire string is actually packed in the
|
|
|
|
|
8 bytes of a LUA_LIGHTUSERDATA. See the markdown
|
|
|
|
|
document, "Tokens-a-new-Lua-Type." LuaStack is the
|
|
|
|
|
module that implements and supports tokens.
|
|
|
|
|
|
|
|
|
|
* We have patched the lua runtime in quite a few ways.
|
|
|
|
|
See the markdown "A-Summary-of-our-Lua-Patches." Some of
|
|
|
|
|
these patches have APIs which are accessible via LuaStack.
|
|
|
|
|
|
|
|
|
|
* Table Subtypes. One of our patches allows you to assign
|
|
|
|
|
table subtypes: tables can be general tables, classes,
|
|
|
|
|
tangibles, and several other specialized types. LuaStack
|
|
|
|
|
provides the enum for all the table types, and the
|
|
|
|
|
accessors to get and set the extended type of a table.
|
|
|
|
|
|
|
|
|
|
* Classes. We have added the concept of a "class" to lua.
|
|
|
|
|
The lua registry contains a table of all classes.
|
|
|
|
|
LuaStack implements the functions to create classes,
|
|
|
|
|
lookup classes, get class names, and the like.
|
|
|
|
|
Classes will be documented in another markdown document.
|
|
|
|
|
|
|
|
|
|
* Tangible basics. Tangibles are a complex data structure
|
|
|
|
|
which is in class World, which is *way* above LuaStack's
|
|
|
|
|
abstraction level. However, LuaStack does implement
|
|
|
|
|
one tiny part of the tangible infrastructure: it provides
|
|
|
|
|
the tangibles table in the lua registry, and functions
|
|
|
|
|
to look up tangibles. The rest of what tangibles are about
|
|
|
|
|
are implemented by the World module.
|
|
|
|
|
|