Lecture 20 | What are the applications of scripting languages in game development?

Last time, we talked about how to embed a scripting language in a game, and the language we used was Lua. The Lua language is lightweight and fast, and the API call is also very convenient and intuitive. Now, we still take the Lua script and try to apply it to the game we develop.

We use C language to do an in-depth analysis of Lua script binding, and then take a look at what the script language can do after binding the script language in game development.

First of all, we need to understand that in factany module can be written using a scripting language. Of course, in the process of game development, there needs to be a clear division of labor. If there is no division of labor, the efficiency may be low.

In situations where certain efficiency requirements are very high, C, C++ or ASM language is generally used to build the underlying modules, and then separate some logical parts for scripting language processing. For example, on the server side that we are familiar with, you can use C/C++ to write server-side IOCP or epoll processing; while receiving, sending, logical processing, etc. can all be written using binding scripts.

During the writing process, we need to have an understanding of the C/C++ language and code. We need to consider this function first.

int test_func(lua_State *L)
{
     return 0;
}

This is just an empty C function. In this function, we see that its incoming parameter is lua_State, which accepts a pointer L. Subsequently, this function returns a 0.

lua_State is the object pointer of the Lua virtual machine. That is, we need to pass in a virtual machine that was previously called new to ensure that the same virtual machine is used in this function.

The function of this function is: As long as it is registered in the Lua virtual machine, it is a function of lua. In the lua function, the parameters passed in are determined internally by the function.

For example, I can write:

int test_func(lua_State *L)
{
     const char *p1 = lua_tostring(L, 1);
     const char *p2 = lua_tostring(L, 2);
     // .... do something
     lua_pushstring(L, "something");
     return 1;
}

Here, lua_tosting is the incoming parameter of this function, which is a string parameter; the second parameter is also a string parameter, and the second parameter 1 or 2 of lua_tosting indicates that it is in the Lua virtual machine. The stack is counted from the bottom of the stack to the top of the stack. Generally, the parameter pushed first is the first one, the parameter pushed later is the second one, and so on. Returning 1 means that this function will return a parameter, which is the content something we pushed after lua_pushstring earlier. This is the returned parameter. So how does this function register as a Lua function?

Let’s look at this code.

lua_register(L, "test", & amp;test_func); 

The function of the lua_register function is to register C functions to the Lua virtual machine. where L is the virtual machine pointer. This is mentioned in the previous code, and the second parameter test is the function name registered in the Lua virtual machine, so the function is called test. The third parameter is the function pointer. We pass the test_func function into the lua_register function. In this way, a function is registered.

So, if we have many functions in the game that need to be registered in Lua, is this writing method too slow? Is there a quick writing method to support registration and other operations?

If you do not have a C/C++ language foundation, or the C/C++ language foundation is relatively weak, the following content may take a while to digest. I will do my best to explain the meaning of the code, but if you are already a C/C++ programmers, then the following code should not be too difficult for you.

We need to use lua_register, let’s first see what parameters it contains. The first one is string, which is char*; the second one is function pointer, which is int ()(lua_State )This form.

Then, we need to define a struct structure, which can be written like this:

 #define _max 256
    typedef struct _ph_func
    {
          char ph_name[_max];
          int (*ph_p_func)(lua_State*);
    } ph_func;

We defined a struct structure. The name of this structure is _ph_func. It doesn’t matter what the name is, but there is a typedef at the beginning. This means that after the structure is declared, the last line of ph_func will replace the originally defined one. The name of ph_func, the result of substitution is that ph_func is equivalent to struct _ph_func, which is often seen in many C language codes.

Next, we see char ph_name[_max]. where the value of _max is 256. I believe you should be able to understand this sentence. The second variable is the function pointer we see, where ph_p_func is the function pointer. The content pointed by the function pointer has not been determined yet. We will assign the value when initializing this structure variable later.

Let’s take a closer look at the contents of these two macros.

#define func_reg(fname) #fname, & ph_##fname
#define func_lua(fname) int ph_##fname(lua_State* L)

Among them, func_reg is used when initializing and assigning a value to the previous structure, because we know that if we need to assign a value to this structure, the code will look like this:

ph_func pobj = {"test", & amp;test_func};

So since we have a large number of functions that need to be registered, we split them into macros, where #fname means changing fname into a string, and ph_##fname means using the ## character to connect the preceding and following contents. stand up.

Through this macro, for example, if we enter an a and assign it to fname, then #fname will become the string “a”. Through ph_##fname, the result will be ph_a.

The following code is convenient for writing Lua registration functions one by one in the code, so obviously, like the above macro, we only need to enter a, then the function becomes int ph_a(lua_State* L);

After defining these two macros, how do we apply them?

func_lua(test_func);
          
ph_func p_funcs[] =
{
      { func_reg(test_func) },
};
func_lua(test_func)
{
     const char *p1 = lua_tostring(L, 1);
     const char *p2 = lua_tostring(L, 2);
     // .... do something
     lua_pushstring(L, "something");
     return 1;
}
void register_func(lua_State* L)
{
      int i;
      for(i=0; i<sizeof(p_funcs)/sizeof(p_funcs[0]); i + + )
      lua_register(L, p_funcs[i].ph_name, p_funcs[i].ph_p_func);
}

First, contact the above macro. The first line of code uses func_lua, so the macro parameter input to func_lua is test_func. So, through this macro, the function name we finally get is int ph_test_func(lua_State* L);.

ph_func p_funcs[] =
{
      { func_reg(test_func) },
};

This code uses the func_reg macro. test_func ends up inside the macro, becoming “test_func”, and the & ph_test_func function pointer.

Finally, let’s look at an important function, register_func. This function will input a Lua virtual machine pointer later, but we need to know what it does inside the function.

int i;
for(i=0; i<sizeof(p_funcs)/sizeof(p_funcs[0]); i + + )
      lua_register(L, p_funcs[i].ph_name, p_funcs[i].ph_p_func)

In the loop, we calculate the length of the structure array of p_funcs. How do we calculate it?

First, we use the sizeof compiler built-in function to get the length of the entire array of p_funcs. The entire length is equal to the value of sizeof(ph_func) multiplied by the array length. The ph_func structure has a string array, each array length is 256, plus a function pointer is 4 bytes long, so the length is 260. And if there are two array elements, that’s the length of 520.

By analogy, /sizeof(p_funcs[0] means that we take the length of the first array as the dividend. In fact, it is the length of the structure itself, so it is the total length of the structure array divided by the length of the structure, which is the total How many array elements are there, followed by a loop.

During the loop, we see that we have filled in the two variables ph_name and ph_p_func in the structure. In this way, we only need to add some tricks through macros to register all Lua functions in C In the program, if we assume that this C program is a game, then we can easily communicate with Lua.

Summary

Let’s summarize what we talked about today.

In the process of combining Lua and C, the C language needs to create a new Lua virtual machine, and then use the pointers of the virtual machine to operate Lua functions.

In the application of the program, using some macro techniques in the C language can make the code conveniently applied in the program.