Reformat 'Our In House Lua API'
This commit is contained in:
@@ -1,204 +1,255 @@
|
||||
### Our In-House Lua API
|
||||
|
||||
Lua comes with a C API, which makes it possible to write C functions that manipulate Lua data. The standard API is functional, but it has some readability issues and can be difficult to work with. Therefore, we have created our own replacement API, which we call "LuaStack." In our codebase, you are expected to use LuaStack, not the standard Lua API. This paper will explain LuaStack using the following example function in Lua, which compares two tables to see if they are exactly equal:
|
||||
Lua comes with a C API, which makes it possible to write C
|
||||
functions that manipulate Lua data. The standard API is
|
||||
functional, but it has some readability issues and can be
|
||||
difficult to work with. Therefore, we have created our own
|
||||
replacement API, which we call "LuaStack." In our codebase,
|
||||
you are expected to use LuaStack, not the standard Lua API.
|
||||
This paper will explain LuaStack using the following example
|
||||
function in Lua, which compares two tables to see if they
|
||||
are exactly equal:
|
||||
|
||||
```lua
|
||||
function table.equal(table1, table2)
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
end
|
||||
```
|
||||
|
||||
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 the number of key-value pairs in a table. Once you fully understand the Lua code above, have a look at the following C++ code, which does the same thing. Don't worry if you don't understand everything yet:
|
||||
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
|
||||
the number of key-value pairs in a table. Once you fully
|
||||
understand the Lua code above, have a look at the following
|
||||
C++ code, which does the same thing. Don't worry if you
|
||||
don't understand everything yet:
|
||||
|
||||
```cpp
|
||||
LuaDefine(table_equal, "(table1, table2)", "Return true if two tables are equal") {
|
||||
|
||||
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.equal(size1, size2)) {
|
||||
|
||||
LS.set(equalflag, false);
|
||||
|
||||
return LS.result();
|
||||
|
||||
}
|
||||
|
||||
LS.set(key, LuaNil);
|
||||
|
||||
while (LS.next(key, value1, table1)) {
|
||||
|
||||
LS.rawget(value2, table2, key);
|
||||
|
||||
if (!LS.equal(value1, value2)) {
|
||||
|
||||
LS.set(equalflag, false);
|
||||
|
||||
return LS.result();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
LS.set(equalflag, true);
|
||||
|
||||
return LS.result();
|
||||
|
||||
LuaDefine(table_equal, "table1, table2", "Return true if two tables are equal") {
|
||||
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();
|
||||
}
|
||||
```
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
## Class LuaArg, Class LuaVar, and Class LuaRet
|
||||
|
||||
In C++ example function, the first few lines inside the body of the LuaDefine are:
|
||||
In C++ example function, the first few lines inside the body
|
||||
of the LuaDefine are:
|
||||
|
||||
```cpp
|
||||
LuaArg table1, table2;
|
||||
|
||||
LuaVar size1, size2, key, value1, value2;
|
||||
|
||||
LuaRet equalflag;
|
||||
```
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
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:
|
||||
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:
|
||||
|
||||
```
|
||||
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
|
||||
```
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
Here are the C++ declarations of class LuaArg, class LuaRet, and class LuaVar, with methods omitted:
|
||||
Here are the C++ declarations of class LuaArg, class LuaRet,
|
||||
and class LuaVar, with methods omitted:
|
||||
|
||||
```cpp
|
||||
class LuaSlot { int stack_position; };
|
||||
|
||||
class LuaSlot { int index_; };
|
||||
class LuaArg : public LuaSlot {};
|
||||
|
||||
class LuaVar : public LuaSlot {};
|
||||
|
||||
class LuaRet : public LuaSlot {};
|
||||
```
|
||||
|
||||
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 stack_position.
|
||||
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_.
|
||||
|
||||
In our example function, once the LuaSlots are initialized, the stack_position values are as follows:
|
||||
In our example function, once the LuaSlots are initialized,
|
||||
the index_ values are as follows:
|
||||
|
||||
```
|
||||
equalflag.stack_position == 1
|
||||
|
||||
size1.stack_position == 2
|
||||
|
||||
size2.stack_position == 3
|
||||
|
||||
key.stack_position == 4
|
||||
|
||||
value1.stack_position == 5
|
||||
|
||||
value2.stack_position == 6
|
||||
|
||||
table1.stack_position == 7
|
||||
|
||||
table2.stack_position == 8
|
||||
equalflag.index_ == 1
|
||||
size1.index_ == 2
|
||||
size2.index_ == 3
|
||||
key.index_ == 4
|
||||
value1.index_ == 5
|
||||
value2.index_ == 6
|
||||
table1.index_ == 7
|
||||
table2.index_ == 8
|
||||
```
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
## The LuaDefStack Constructor
|
||||
|
||||
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?
|
||||
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?
|
||||
|
||||
It's not in the LuaSlot constructor. The LuaSlot constructor doesn't do anything but set stack_position 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.
|
||||
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.
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
In our example C++ function, you'll find the LuaDefStack constructor immediately after the LuaSlot declarations:
|
||||
In our example C++ function, you'll find the LuaDefStack
|
||||
constructor immediately after the LuaSlot declarations:
|
||||
|
||||
```cpp
|
||||
LuaDefStack LS(L, table1, table2, size1, size2, key, value1, value2, equalflag);
|
||||
```
|
||||
|
||||
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.
|
||||
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.
|
||||
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 stack_position.
|
||||
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.
|
||||
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.
|
||||
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 stack_positions, and via that level of indirection, the LuaSlots all contain the correct lua values.
|
||||
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.
|
||||
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.
|
||||
|
||||
## Methods of Class LuaDefStack
|
||||
|
||||
@@ -208,13 +259,17 @@ In our example function, you'll find this line of code:
|
||||
LS.set(size1, LS.nkeys(table1));
|
||||
```
|
||||
|
||||
Let's break that down into pieces. Here's a simpler statement, using just LS.set:
|
||||
Let's break that down into pieces. Here's a simpler
|
||||
statement, using just LS.set:
|
||||
|
||||
```cpp
|
||||
LS.set(size1, 3);
|
||||
```
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
The function LS.set is a call to the following method:
|
||||
|
||||
@@ -222,113 +277,142 @@ The function LS.set is a call to the following method:
|
||||
void LuaDefStack::set(const LuaSlot &slot, int value) const;
|
||||
```
|
||||
|
||||
Inside that method, *this* contains the pointer to the lua stack, *slot* contains the necessary stack_position, and *value* contains the value to store. What this method actually does is set stack[stack_position] = value. In our example function we know that *size1.stack_position* == 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.
|
||||
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.
|
||||
|
||||
OK, now, let's consider this statement from the example function:
|
||||
OK, now, let's consider this statement from the example
|
||||
function:
|
||||
|
||||
```cpp
|
||||
LS.set(size1, LS.nkeys(table1));
|
||||
```
|
||||
|
||||
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*.
|
||||
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*.
|
||||
|
||||
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:
|
||||
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:
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
For a complete listing of all the operators in LuaDefStack, have a look at the header file luastack.hpp, at the class LuaBaseStack. There, you will find prototypes for all the functions, along with documentation for each one.
|
||||
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.
|
||||
|
||||
## Converting Lua Values to C++ Values
|
||||
|
||||
Suppose that you have a function, callable from lua, that is supposed to accept a string as a parameter:
|
||||
Suppose that you have a function, callable from lua, that is
|
||||
supposed to accept a string as a parameter:
|
||||
|
||||
```cpp
|
||||
LuaDefine(frob, (str), "Frobnicate a string.") {
|
||||
|
||||
LuaArg str;
|
||||
|
||||
LuaDefStack LS(L, str);
|
||||
|
||||
…etc…
|
||||
LuaDefine(frob, "str", "Frobnicate a string.") {
|
||||
LuaArg str;
|
||||
LuaDefStack LS(L, str);
|
||||
…etc…
|
||||
```
|
||||
|
||||
The variable 'str' is a LuaSlot. Suppose you want to convert that to a C++ string. LuaDefStack provides a method that can do that:
|
||||
The variable 'str' is a LuaSlot. Suppose you want to convert
|
||||
that to a C++ string. LuaDefStack provides a method that can
|
||||
do that:
|
||||
|
||||
```cpp
|
||||
eng::string s = LS.ckstring(str);
|
||||
```
|
||||
|
||||
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:
|
||||
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:
|
||||
|
||||
```cpp
|
||||
std::string s = LS.ckstring(str, "str");
|
||||
```
|
||||
|
||||
Class LuaDefStack provides a full catalog of conversion functions that return different C++ data types:
|
||||
Class LuaDefStack provides a full catalog of conversion
|
||||
functions that return different C++ data types:
|
||||
|
||||
```cpp
|
||||
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;
|
||||
|
||||
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;
|
||||
std::string_view ckstringview(LuaSlot s, const char *argname = "value") const;
|
||||
|
||||
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;
|
||||
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.
|
||||
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:
|
||||
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:
|
||||
|
||||
```cpp
|
||||
std::optional<eng::string> s = LS.trystring(str);
|
||||
```
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
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:*
|
||||
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:*
|
||||
|
||||
```cpp
|
||||
bool b = LS.isstring(str);
|
||||
```
|
||||
|
||||
You can also use LS.type to fetch the type of a lua value as an enum.
|
||||
You can also use LS.type to fetch the type of a lua value as
|
||||
an enum.
|
||||
|
||||
## Dealing with Return Values
|
||||
|
||||
@@ -336,178 +420,298 @@ Our sample function contains this code:
|
||||
|
||||
```cpp
|
||||
LuaRet equalflag;
|
||||
|
||||
…
|
||||
|
||||
if (!LS.equal(size1, size2)) {
|
||||
|
||||
LS.set(equalflag, false);
|
||||
|
||||
return LS.result();
|
||||
|
||||
if (!LS.rawequal(size1, size2)) {
|
||||
LS.set(equalflag, false);
|
||||
return LS.result();
|
||||
}
|
||||
```
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
So, in every LuaDefine, the process of returning from a function always consists of two steps:
|
||||
So, in every LuaDefine, the process of returning from a
|
||||
function always consists of two steps:
|
||||
|
||||
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()*
|
||||
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()*
|
||||
|
||||
## The LuaDefine Macro
|
||||
|
||||
Our sample function starts with this line of code:
|
||||
|
||||
```cpp
|
||||
LuaDefine(table_equal, "(table1, table2)", "Return true if two tables are equal")
|
||||
LuaDefine(table_equal, "table1, table2", "Return true if two tables are equal")
|
||||
```
|
||||
|
||||
The two strings in this line of code 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 two strings in this line of code 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.
|
||||
|
||||
LuaDefine is a C preprocessor macro. Before I tell you what it macro expands into, I'd like to tell you how Lua functions are normally written using the standard Lua API. When using the standard API, lua functions look like this:
|
||||
LuaDefine is a C preprocessor macro. Before I tell you what
|
||||
it macro expands into, I'd like to tell you how Lua
|
||||
functions are normally written using the standard Lua API.
|
||||
When using the standard API, lua functions look like this:
|
||||
|
||||
```cpp
|
||||
int table_equal(lua_State *L) {
|
||||
|
||||
…
|
||||
|
||||
…
|
||||
}
|
||||
```
|
||||
|
||||
The C prototype is always the same: the return value is always *int,* and the only argument is *lua_State*L*, a pointer to the lua stack. A C function must have this prototype to be compatible with the lua runtime.
|
||||
The C prototype is always the same: the return value is
|
||||
always *int,* and the only argument is *lua_State*L*, a
|
||||
pointer to the lua stack. A C function must have this
|
||||
prototype to be compatible with the lua runtime.
|
||||
|
||||
Next, I will show you the macro expansion of a LuaDefine to help you understand what it does. Here is the LuaDefine that I will macro expand:
|
||||
Next, I will show you the macro expansion of a LuaDefine to
|
||||
help you understand what it does. Here is the LuaDefine that
|
||||
I will macro expand:
|
||||
|
||||
```cpp
|
||||
LuaDefine(table_equal, "(table1, table2)", "Return true if two tables are equal")
|
||||
LuaDefine(table_equal, "table1, table2", "Return true if two tables are equal")
|
||||
```
|
||||
|
||||
Here is the expansion:
|
||||
|
||||
```cpp
|
||||
int lfn_table_equal(lua_State *L);
|
||||
|
||||
const char *lfnarg_table_equal = "(table1, table2)";
|
||||
|
||||
const char *lfnarg_table_equal = "table1, table2";
|
||||
const char *lfndoc_table_equal = "Return true if two tables are equal";
|
||||
|
||||
LuaFunctionReg reg_table_equal
|
||||
|
||||
("table_equal", lfnarg_table_equal, lfndoc_table_equal, false, lfn_table_equal);
|
||||
|
||||
LuaFunctionReg reg_table_equal(
|
||||
"table_equal", lfnarg_table_equal, lfndoc_table_equal, false, lfn_table_equal);
|
||||
int lfn_table_equal(lua_State *L)
|
||||
```
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
Next, there are the two documentation strings. I've given these documentation strings two names: lfnarg_table_equal and lfndoc_table_equal. That way, it's possible to access these documentation strings in code.
|
||||
Next, there are the two documentation strings. I've given
|
||||
these documentation strings two names: lfnarg_table_equal
|
||||
and lfndoc_table_equal. That way, it's possible to access
|
||||
these documentation strings in code.
|
||||
|
||||
Next, there is the "registry entry" reg_table_equal, of class LuaFunctionReg. The constructor of reg_table_equal 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, the constructor links reg_table_equal into a global linked list of LuaFunctionReg objects. This linked list makes up the registry of all LuaDefines.
|
||||
Next, there is the "registry entry" reg_table_equal, of
|
||||
class LuaFunctionReg. The constructor of reg_table_equal
|
||||
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, the constructor links
|
||||
reg_table_equal into a global linked list of LuaFunctionReg
|
||||
objects. This linked list makes up the registry of all
|
||||
LuaDefines.
|
||||
|
||||
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 by the function body.
|
||||
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
|
||||
by the function body.
|
||||
|
||||
The upshot of all this is that LuaDefine doesn't just declare a function. It also links that function into a global registry of all the 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.
|
||||
The upshot of all this is that LuaDefine doesn't just
|
||||
declare a function. It also links that function into a
|
||||
global registry of all the 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.
|
||||
|
||||
The registry of all LuaDefines has two primary uses:
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
2. The documentation system traverses the registry to find the documentation strings. It uses these strings to auto-generate the manual.
|
||||
2. The documentation system traverses the registry to find
|
||||
the documentation strings. It uses these strings to
|
||||
auto-generate the manual.
|
||||
|
||||
## Class LuaExtStack
|
||||
|
||||
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 hardwired to assume that a caller *exists*:
|
||||
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
|
||||
hardwired to assume that a caller *exists*:
|
||||
|
||||
- It checks that the caller passed the right number of arguments.
|
||||
- It checks that the caller passed the right number of
|
||||
arguments.
|
||||
|
||||
- It transfers the arguments from the caller to the LuaArg slots.
|
||||
- It transfers the LuaRet slots back to the caller at function end.
|
||||
- It transfers the arguments from the caller to the LuaArg
|
||||
slots.
|
||||
- It transfers the LuaRet slots back to the caller at
|
||||
function end.
|
||||
|
||||
For situations where there isn't any caller, we have created an alternative to LuaDefStack called LuaExtStack. There are several things that LuaExtStack does differently:
|
||||
For situations where there isn't any caller, we have created
|
||||
an alternative to LuaDefStack called LuaExtStack. There are
|
||||
several things that LuaExtStack does differently:
|
||||
|
||||
- LuaExtStack doesn't accept LuaArg or LuaRet parameters - it only allows LuaVar parameters. That only makes sense, since it should be used in contexts where there's no caller.
|
||||
- LuaExtStack doesn't accept LuaArg or LuaRet parameters -
|
||||
it only allows LuaVar parameters. That only makes sense,
|
||||
since it should be used in contexts where there's no
|
||||
caller.
|
||||
|
||||
- There is no code in LuaExtStack to check that the caller passed the right number of arguments, since again, there is no caller.
|
||||
- There is no code in LuaExtStack to check that the caller
|
||||
passed the right number of arguments, since again, there
|
||||
is no caller.
|
||||
|
||||
- Class LuaExtStack doesn't have a method LS.result, since there's nobody to return a result to.
|
||||
- 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 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.
|
||||
- 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.
|
||||
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.
|
||||
|
||||
## Using Multiple Lua Stacks
|
||||
|
||||
In 95% of the code you'll write, there is only one lua stack. However, there are two big exceptions to this rule.
|
||||
In 95% of the code you'll write, there is only one lua
|
||||
stack. However, there are two big exceptions to this rule.
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
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:
|
||||
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:
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
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:
|
||||
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:
|
||||
|
||||
```cpp
|
||||
MLS.set(stable, 3);
|
||||
```
|
||||
|
||||
That's because stable contains a stack_position on the SLS stack, not a stack_position on the MLS stack. Every LuaSlot is associated with one and only one lua stack, and you must pair them up correctly.
|
||||
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.
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
## Mixing LuaStack with the Standard API
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
Ideally, our LuaStack API should provide a method to compile lua code into a closure. But I haven't gotten around to it yet. Therefore, World::invoke_lua has to use the standard lua API to compile the code. This is an excerpt from World::invoke_lua:
|
||||
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:
|
||||
|
||||
```cpp
|
||||
LuaVar func;
|
||||
|
||||
LuaExtStack LS(L, func);
|
||||
|
||||
int status = luaL_loadbuffer(L, datapack.data(), datapack.size(), "=invoke");
|
||||
|
||||
lua_replace(L, func.stack_position());
|
||||
lua_replace(L, func.index());
|
||||
```
|
||||
|
||||
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:
|
||||
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:
|
||||
|
||||
```
|
||||
stack[1] contains func, whose value is nil
|
||||
```
|
||||
|
||||
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:
|
||||
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:
|
||||
|
||||
```
|
||||
stack[1] contains func, whose value is nil
|
||||
|
||||
stack[2] contains the compiled closure
|
||||
```
|
||||
|
||||
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:
|
||||
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:
|
||||
|
||||
```
|
||||
stack[1] contains func, whose value is the compiled closure
|
||||
```
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
Reference in New Issue
Block a user