Principle and use of tolua wrap

1. What is a wrap file?

Each wrap file is a wrapper for a C# class. In Lua, the C# instance is indirectly operated by calling functions in the wrap class.

2. The overall process of generating and using wrap class files

3. The process of generating a wrap file

This part is mainly completed by analyzing the reflection information of the class.

4. Analysis of wrap file content

Use UnityEngine_GameObjectWrap.cs as an example.

1. Registration part

1 public static void Register(LuaState L)
 2 {
    
    
 3     L.BeginClass(typeof(UnityEngine.GameObject), typeof(UnityEngine.Object));
 4     L.RegFunction("CreatePrimitive", CreatePrimitive);
 5     L.RegFunction("GetComponent", GetComponent);
 6     L.RegFunction("GetComponentInChildren", GetComponentInChildren);
 7     L.RegFunction("GetComponentInParent", GetComponentInParent);
 8     L.RegFunction("GetComponents", GetComponents);
 9     L.RegFunction("GetComponentsInChildren", GetComponentsInChildren);
10     L.RegFunction("GetComponentsInParent", GetComponentsInParent);
11     L.RegFunction("SetActive", SetActive);
12     L.RegFunction("CompareTag", CompareTag);
13     L.RegFunction("FindGameObjectWithTag", FindGameObjectWithTag);
14     L.RegFunction("FindWithTag", FindWithTag);
15     L.RegFunction("FindGameObjectsWithTag", FindGameObjectsWithTag);
16     L.RegFunction("Find", Find);
17     L.RegFunction("AddComponent", AddComponent);
18     L.RegFunction("BroadcastMessage", BroadcastMessage);
19     L.RegFunction("SendMessageUpwards", SendMessageUpwards);
20     L.RegFunction("SendMessage", SendMessage);
21     L.RegFunction("New", _CreateUnityEngine_GameObject);
22     L.RegFunction("__eq", op_Equality);
23     L.RegFunction("__tostring", ToLua.op_ToString);
24     L.RegVar("transform", get_transform, null);
25     L.RegVar("layer", get_layer, set_layer);
26     L.RegVar("activeSelf", get_activeSelf, null);
27     L.RegVar("activeInHierarchy", get_activeInHierarchy, null);
28     L.RegVar("isStatic", get_isStatic, set_isStatic);
29     L.RegVar("tag", get_tag, set_tag);
30     L.RegVar("scene", get_scene, null);
31     L.RegVar("gameObject", get_gameObject, null);
32     L.EndClass();
33 }

This part of the code is generated by GenRegisterFunction(). As you can see, the code is divided into 4 parts:

  • 1.BeginClass part, responsible for the initialization part of the class in Lua
  • 2.RegFunction part, responsible for registering the function into lua
  • 3.RegVar part, responsible for registering variables and properties into lua
  • 4. The EndClass part is responsible for the finishing work of class end registration.

BeginClass section

① Used to create classes and class metatables. If the metatable of a class's metatable (the metatable of a class is the entity that carries the methods and attributes of each class, the metatable of the class's metatable is the parent class of the class)
  ② Will The class is added to the loaded table.
  ③Set the common metamethods and attributes of each class's metatable, __gc, name, ref, __cal, __index, __newindex.

RegFunction section

What each RefFunction does is very simple. It converts each function into a pointer and then adds it to the metatable of the class. It is the same as registering a C function into Lua.

RegVar section

Each variable or attribute may be packaged into get_xxx, set_xxx function registration and added to the gettag and settag tables of the class's metatable for calling and retrieval.

EndClass section

Did two things:
  ① Set the metatable of the class
  ② Add the class to the table represented by the module (such as adding GameObject to the UnityEngine table)

2. The entity part of each function

Since the principles of the constructor, this[], get_xxx, and set_xxx are similar and are all generated through reflected information, we will talk about them together with an example (using the GetComponent function of GameObject to illustrate).

1 [MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))]
 2 static int GetComponent(IntPtr L)
 3 {
    
    
 4     try
 5     {
    
    
 6         //获取栈中参数的个数
 7         int count = LuaDLL.lua_gettop(L);
 8         //根据栈中元素的个数和元素的类型判断该使用那一个重载
 9         if (count == 2 && TypeChecker.CheckTypes<string>(L, 2))
10         {
    
    
11             //将栈底的元素取出来,这个obj在栈中是一个fulluserdata,需要先将这个fulluserdata转化成对应的c#实例,也就是调用这个GetComponent函数的GameObject实例
12             UnityEngine.GameObject obj = (UnityEngine.GameObject)ToLua.CheckObject(L, 1, typeof(UnityEngine.GameObject));
13             //将栈底的上一个元素取出来,也就是GetComponent(string type)的参数
14             string arg0 = ToLua.ToString(L, 2);
15             //通过obj,arg0直接第调用GetCompent(string type)函数
16             UnityEngine.Component o = obj.GetComponent(arg0);
17             //将调用结果压栈
18             ToLua.Push(L, o);
19             //返回参数的个数
20             return 1;
21         }
22         //另一个GetComponent的重载,跟上一个差不多,就不详细说明了
23         else if (count == 2 && TypeChecker.CheckTypes<System.Type>(L, 2))
24         {
    
    
25             UnityEngine.GameObject obj = (UnityEngine.GameObject)ToLua.CheckObject(L, 1, typeof(UnityEngine.GameObject));
26             System.Type arg0 = (System.Type)ToLua.ToObject(L, 2);
27             UnityEngine.Component o = obj.GetComponent(arg0);
28             ToLua.Push(L, o);
29             return 1;
30         }
31         //参数数量或类型不对,没有找到对应的重载,抛出错误
32         else
33         {
    
    
34             return LuaDLL.luaL_throw(L, "invalid arguments to method: UnityEngine.GameObject.GetComponent");
35         }
36     }
37     catch (Exception e)
38     {
    
    
39         return LuaDLL.toluaL_exception(L, e);
40     }
41 }

It can be seen that the content of the GetComponent function is actually generated by analyzing the number of overloads of GetComponent, the number of parameters of each overload, and the type. The specific content is similar to lua calling c function.

3. The actual calling process of each function

Suppose there is such a call in Lua:

1 local tempGameObject = UnityEngine.GameObject("temp")
2 local transform = tempGameObject.GetComponent("Transform")

The actual calling process corresponding to the second line of code is:

  • 1. First go to the metatable GameObject metatable of tempGameObject and try to get the GetComponent function, and you get it.
  • 2. Call the obtained GetComponent function. When calling, tempGameObject, "Transform" will be pushed onto the stack as parameters first, and then the GetComponent function will be called.
  • 3. Next, enter the GetComponent function to operate. Because a new ci is generated, there are only two elements in the stack: tempGameOjbect and "Transfrom".
  • 4. Determine the overload to be used based on the number and type of parameters.
  • 5. Find the corresponding instance in the objects table through the index of the c# instance represented by tempGameObject. At the same time, take out the "Transform" parameter and prepare for the real function call.
  • 6. Execute obj.GetComponent(arg0), wrap the result into a fulluserdata and push it onto the stack, ending the call.
  • 7. The transfrom variable in Lua is assigned the fulluserdata pushed onto the stack.
  • 8. End.

Among them, operations 3-7 are all performed in c#, which is the GetComponent function in the wrap file.

5. What does a class look like after it is registered into the Lua virtual machine through the wrap file?

Use GameObjectWrap as an example.

[The external link image transfer failed. The source site may have an anti-leeching mechanism. It is recommended to save the image and upload it directly (img-oqgCW61A-1675731847578)(https://www.liangtengyu.com:9998/images/pic_2eff0886.png)]

It can be seen that all functions of GameObject are implemented through a metatable. Through this metatable, various functions in the GameObjectWrap file can be called to implement operations on GameObject instances. This metatable is invisible to the user because We usually only call the GameObject class and GameObject instance in the code, and do not directly reference this metatable. Next, let’s analyze the relationship between the GameObject class, GameObject instance and this metatable:

  • GameObject class: In fact, it is just a table that is placed in the _G table and serves as an index for people to call. We use it to trigger various meta-methods of the GameObject metatable to realize the use of the c# class.
  • An instance of GameObject: It is a fulluserdata, and the content is an integer. This integer represents the index of this instance in the objects table (objects is a recycling linked list implemented using list, and the c# class instances called in Lua are stored in this, which will be discussed later. Talking about this objects table), every time a method of a C# instance is called in Lua, the corresponding instance of this index in C# will be found through this index, and then the operation will be performed, and finally the operation result will be converted into a fulluserdata (or Lua's internal Create a type, such as bool, etc.) and push it onto the stack to end the call.

6. The process of calling a function or variable in a C# instance in Lua

local tempGameObject = UnityEngine.GameObject("temp")
local instanceID = tempGameObject.GetInstanceID()

[The external link image transfer failed. The source site may have an anti-leeching mechanism. It is recommended to save the image and upload it directly (img-9U24RTNn-1675731847578)(https://www.liangtengyu.com:9998/images/pic_0b3f23c1.png)]

After understanding the GameObject metatable, these are just some basic metatable operations and will not be explained in detail.

7. The real storage location of the c# instance in Lua

As mentioned earlier, each C# instance in Lua is a fulluserdata whose content is an integer index. When making a function call, the functions and variables of the instance represented by this index are searched and called through this integer index. The process of generating or using a Lua variable representing a C# instance is roughly like this. Also use this example to illustrate:

local tempGameObject = UnityEngine.GameObject("temp")
local transform = tempGameObject.GetComponent("Transform")

Therefore, the C# instances called and created in Lua are actually stored in the objects table in C#. The variable in Lua is just a fulluserdata that holds the index position of the C# instance, and does not directly reference the C# instance.
Function calls and variable modifications to C# instances are performed through metatable calls to functions in the wrap file. The above is the principle of how C# classes are used in Lua through the wrap class.

Guess you like

Origin blog.csdn.net/2301_76379420/article/details/129096466
Recommended