ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

(转)ToLua源码分析:启动流程

2021-12-31 15:03:29  阅读:160  来源: 互联网

标签:LuaState UnityEngine 流程 require lua 源码 ToLua tolua


 

说明


临时工先顶上来,回头整理施工。发现流水账叙述比较无趣和难懂,后面考虑更换形式。
ToLua版本1.0.6。
第一篇启动不深入过多细节,后面对特性进行深入解析。
部分代码进行了抽取,以c#、c、lua形式混写。实际以源码为准。
系列前置关卡:

  Lua语言。
  Unity使用经验。
  Lua与宿主语言交互经验。
  ToLua接入和使用经验。
  tolua Unity工程和tolua_runtime源码(不给下载链接,搜索和查阅资料是一项基本功)。

  ToLua基于LuaInterface,LuaInterface是一个实现lua和微软.Net平台的CLR混合编程的开源库,使得lua脚本可以实例化CLR对象,访问属性,调用方法甚至使用lua函数来处理事件。ToLua保留了LuaInterface基本形式,重写或移除了部分内容,使代码更加简洁,提供了对Unity的支持、拓展了lua5.1.4源码。而最大的改进在于,LuaInterface中lua访问CLR需要运行时反射,对于游戏应用来说效率不够理想,ToLua则提供了一套中间层导出工具,对于需要访问的CLR、Unity及自定义类预生成wrap文件,lua访问时只访问wrap文件,wrap文件接收lua传递来的参数,进行类型(值、对象、委托)转换,再调用真正工作的CLR对象和函数,最后将返回值返回给lua,有效地提高了效率。

核心功能及文件


提供Lua-c#值类型、对象类型转化操作交互层。(ObjectTranslator.cs、LuaFunction.cs、LuaTable.cs、ToLua.cs等)
提供Lua虚拟机创建、启动、销毁,Require、DoFile、DoString、Traceback等相关支持。(LuaState.cs、LuaStatic.cs)
提供导出工具,利用c#反射,对指定的c#类生成对应的wrap文件,启动后将所有wrap文件注册到lua虚拟机中。(ToLuaMenu.cs、ToLuaExport.cs、ToLuaTree.cs、LuaBinder.cs、CustomSetting.cs等)
提供c#对象和lua userdata对应关系,使该userdata能访问对应c#对象属性,调用对应c#对象函数。lua支持一定的面向对象(类、继承)。管理这些对象的内存分配与生命周期、GC。(LuaState.cs)
提供支持功能Lua Coroutine、反射等,Lua层重写部分性能有问题对象如Vector系列。(Vector3.lua等)

启动代码


  ToLua启动只需要三句代码即可。但里面实际经历了一段漫长的日夜。

void Start() 
{
    lua = new LuaState();  // 启动1:创建tolua提供的LuaState对象。              
    lua.Start();           // 启动2:虚拟机初始化。
    LuaBinder.Bind(lua);   // 启动3:Lua-c#中间层wrap文件们分别向虚拟机注册自己。
}

 

启动1 :lua new LuaState()

实例化LuaState对象。类的成员属性按子类基类的顺序初始化,类的构造函数按基类子类顺序执行。


LuaState继承自LuaStatePtr,该类包含一个System.IntPtr L指针,即lua虚拟机栈,并提供了一系列LuaDLL API的封装,可以认为是LuaDLL的升级版。而成员属性中比较重要的有ObjectTranslator和LuaReflection,暂时我们只用关注ObjectTranslator。

// LuaState.cs
public class LuaState : LuaStatePtr, IDisposable {
    public ObjectTranslator translator = new ObjectTranslator();
    public LuaReflection reflection = new LuaReflection();

    public LuaState() 
    {
        ...
    }
}

 

启动1.1 : ObjectTranslator

ObjectTranslator主要用于缓存lua需要访问的c# object对象。提供了对象池以及添加、查询、删除对象方法。管理对象的生命周期。LuaObjectPool对象池包含了一个PoolNode对象列表进行循环复用,PoolNode对象包含对真正object的引用,以及一个当前空闲索引的链表,其策略类似于lua_ref。

public class ObjectTranslator {

    //gc打印标志
    public bool LogGC { get; set; } 

    // 静态单例
    private static ObjectTranslator _translator = null;  

    // object对象在对象池中的索引。
    public readonly Dictionary<object, int> objectsBackMap = 
             new Dictionary<object, int>(new CompareObject());   

    // object对象池。
    public readonly LuaObjectPool objects = new LuaObjectPool();     

     // 延迟gc列表,目前只在GameObject.Destroy(go, float t)传递了参数t时使用。
    private List<DelayGC> gcList = new List<DelayGC>();

    public ObjectTranslator()
    {
        // 是否在RemoveObject时,打印信息
        LogGC = false;  
        // 单例
        _translator = this;  
    }

    // 获得单例
    public static ObjectTranslator Get(IntPtr L) {
        return _translator;
    }

    // 缓存一个object,返回在LuaObjectPool中的索引。
    public int AddObject(object obj) {...}

    // 根据索引,移除一个object缓存。
    public void RemoveObject(int udata) {...}

    // 根据索引,获得一个已缓存的object对象。
    public object GetObject(int udata)
    {
        return objects.TryGetValue(udata);         
    }
}

 

启动1.2 : LuaState构造

public LuaState()            
{
    if (mainState == null)
    {
        mainState = this;
    }
    /* LuaException继承自Exception,用于出错堆栈信息,
       tolua发生错误时,会throw new LuaException("xxxx"),
       LuaException内部会将堆栈格式化。
     */
    LuaException.Init(); 
    L = LuaNewState(); // = LuaDLL.luaL_newstate(),启动lua虚拟机。           
    OpenToLuaLibs();    // 见构造1
    ToLua.OpenLibs(L);  // 见构造2
    OpenBaseLibs();     // 见构造3                   
    LuaSetTop(0);       // 栈顶设置个0保险
    InitLuaPath();      // 见构造4
}

 

构造 1:ToLua.OpenLibs(L);

调用了LuaDLL.tolua_openlibs(L),c源码如下:

LUALIB_API void tolua_openlibs(lua_State *L)
{   
    initmodulebuffer();
    luaL_openlibs(L);   
    int top = lua_gettop(L);    

    tolua_setluabaseridx(L);    
    tolua_opentraceback(L);
    tolua_openpreload(L);
    tolua_openubox(L);
    tolua_openfixedmap(L);    
    tolua_openint64(L);
    tolua_openuint64(L);
    tolua_openvptr(L);    
    //tolua_openrequire(L);

    luaL_register(L, "Mathf", tolua_mathf);     
    luaL_register(L, "tolua", tolua_funcs);    

    lua_getglobal(L, "tolua");

    lua_pushstring(L, "gettag");
    lua_pushlightuserdata(L, &gettag);
    lua_rawset(L, -3);

    lua_pushstring(L, "settag");
    lua_pushlightuserdata(L, &settag);
    lua_rawset(L, -3);

    lua_pushstring(L, "version");
    lua_pushstring(L, "1.0.5");
    lua_rawset(L, -3);

    lua_settop(L,top);
}

 

initmodulebuffer(): 初始化了一个 static stringbuffer sb,用于缓存命名空间。
luaL_openlibs(L): 初始化lua基本库,math、debug、io、os等。
tolua_setluabaseridx: 在Lua注册表中预留了1-64,注册了以下几项 ,用于C#层直接获取
  #define LUA_RIDX_MAINTHREAD   1 ->  L // 表示定义了宏,并在相应注册表中存储了Lua栈L,以下含义类似。
  #define LUA_RIDX_GLOBALS     2  ->  G
  #define LUA_RIDX_REQUIRE     19 -> require
tolua_opentraceback :
   _G[“traceback”] = debug.traceback
  #define LUA_RIDX_TRACEBACK 3 -> debug.traceback
tolua_openpreload:
  #define LUA_RIDX_PRELOAD 25 -> package.preload
  #define LUA_RIDX_LOADED 26 -> package.loaded
tolua_openubox: 创建了一个值弱表
  #define LUA_RIDX_UBOX 4 -> setmetatable({}, {__mode = “v”})
tolua_openfixedmap: 创建了一个fixedmap table
  #define LUA_RIDX_FIXEDMAP 5 -> {}
tolua_openint64(L): 声明并注册了int64元表
  #define LUA_RIDX_INT64 20 -> global[“int64”] = package[“int64”] = {int64}
tolua_openuint64(L): 声明并注册了uint64元表
  #define LUA_RIDX_UINT64 27 -> global[“uint64”] = package[“uint64”] = {uint64}
tolua_openvptr(L): 声明注册了vptr元表
  #define LUA_RIDX_VPTR 21 -> { __index = vptr_index_event; __newindex = vptr_newindex_event}
luaL_register(L, “Mathf”, tolua_mathf): 添加了一些Mathf相关函数
luaL_register(L, “tolua”, tolua_funcs): 添加了一些tolua拓展函数
tolua.gettag = &gettag 向tolua中存了一些值。
tolua.settag = &settag
tolua.version = “1.0.5”

构造2 :ToLua.OpenLibs(L)

向lua虚拟机注册了一些基础的c函数。

// ToLua.cs line:55
public static void OpenLibs(IntPtr L) {

    // AddLuaLoader = table.insert(package.loaders, ToLua.Loader, 2)
    AddLuaLoader(L) 

    /* 设置虚拟机崩溃时处理函数 */
    LuaDLL.tolua_atpanic(L, Panic);

    /* 向lua注册了一些全局函数 */
    _G["print"] = ToLua.Print
    _G["doFile"] = ToLua.DoFile
    _G["loadfile"] = ToLua.Loadfile

    /* 向tolua table里注册了一些函数 */
    tolua["isnull"] = ToLua.IsNull
    tolua["typeof"]= ToLua.GetClassType  /* Lua.AddComponet(typeof(Transform)) */
    tolua["tolstring"]= ToLua.BufferToString
    tolua["toarray"] = ToLua.TableToArray
    tolua["null"]= newudate()        
    _G["null"] = tolua.null  
}

 

构造3 :OpenBaseLibs

tolua会事先生成一些Base Wrap File,也就是.Net或者Unity的基本类型中间层,由于这些类型的重要性,所以在这里先注册。注册之后,Lua虚拟机就可以访问这些类。

    /* 向lua注册了一些基本类 */
    void OpenBaseLibs() {
        Register(System.Object); 
        Register(LuaInterface.NullObject);  // regster domain-> System.null
        Register(System.String, System.Object);  
        Register(System.Delegate, System.Object);  
        Register(System.Enum);
        Register(System.Array, System.Object);
        Register(System.Type, System.Object);
        Register(System.Collections.IEnumerator);
        Register(System.Collections.ObjectModel.ReadOnlyCollection);
        Register(System.Collections.Generic.List);
        Register(System.Collections.Generic.Dictionary);
        Register(System.Collections.Generic.Dictionary.KeyCollection);
        Register(System.Collections.Generic.Dictionary.ValueCollection);

        package.preload["tolua.out"] = LuaInterface_LuaOutWrap.LuaOpen_ToLua_Out;

        Register(LuaInterface.EventObject, System.Object);
        Register(UnityEngine.Object, System.Object);
        Register(UnityEngine.Coroutine);

        LuaUnityLibs.OpenLibs(L);            
        LuaReflection.OpenLibs(L);
    }

 

接着调用了LuaUnityLibs.OpenLibs和LuaReflection.OpenLibs:

    public static void OpenLibs(IntPtr L)
    {

        InitMathf(L);  // 初始化了Mathf库
        InitLayer(L);
    }

    /* 向tolua table注册了一些反射相关的方法。*/
    public static void OpenLibs(IntPtr L) 
    {
        tolua.findtype = LuaReflection.FindType;
        tolua.loadassembly= LuaReflection.LoadAssembly;
        tolua.getmethod= LuaReflection.GetMethod;
        tolua.getconstructor= LuaReflection.GetConstructor;
        tolua.gettypemethod= LuaReflection.GetTypeMethod;
        tolua.getfield= LuaReflection.GetField;
        tolua.getproperty= LuaReflection.GetProperty;
        tolua.createinstance= LuaReflection.CreateInstance'
        package.preload.reflection = LuaReflection.OpenReflectionLibs;
    }

其中OpenLibs中InitLayer主要是将Unity中的Layer注册到lua虚拟机中。lua中使用Layer.name来获取索引。

 

构造4 :InitLuaPath

  这是LuaState构造函数中做的最后一件事情,主要是初始化lua虚拟机require、import、DOFile文件的搜索路径。
核心类LuaFileUtils,该类主要记录了所有的搜索路径,提供了从这些路径搜索出文件加载的方法。而这里调用的方法是LuaState.AddSearchPath,看名字即知道是向LuaFileUtils添加一个搜索路径。这些默认路径可以在LuaConst.cs中找到。

InitPackagePath()  // lua自带一些搜索路径,在package.path,这句是将这些路径取出添加给LuaFileUtils
AddSearchPath(LuaConst.toluaDir); // editor only: 默认 Application.dataPath + "/Lua";
AddSearchPath(LuaConst.luaDir);    // editor only: 默认 Application.dataPath + "/ToLua/Lua";

/* string.Format("{0}/{1}/Lua", Application.persistentDataPath, osDir) */
AddSearchPath(LuaConst.luaResDir); 

 

new LuaState()总结

  1. 初始化了LuaState成员属性ObjectTranslator和LuaReflection。
  2. LuaState构造开始。
  3. 构造中先初始化了LuaException用于异常时抛出,提供了出错信息堆栈的格式化显示。
  4. 以标准的LuaDLL.luaL_newstate语句正式启动lua虚拟机。
  5. 继续教科书般的开局,luaL_openlibs开启lua基本库,额外地在c层初始了tolua的一些相关内容。
  6. OpenToLuaLibs向lua虚拟机注册ToLua.Print、ToLua.DoFile、Panic等基础c函数。
  7. OpenBaseLibs先向虚拟机注册了一些System和Unity命名空间下的基础类,接着初始化了Mathf、Layer和一些反射相关。
  8. InitLuaPath在LuaFileUtils中存储了lua中package.path、LuaConst中一些路径搜索路径。用于查找文件。

  9. LuaState构造结束。


个人觉得6、7的函数命名有些容易混淆。7最后做的事情也与6非常类似,代码的职责有些混乱。
接下来就是启动的第二步——LuaState.Start()。

启动2:LuaState.Start()

启动后面两步做的事情相对简单,LuaState.Start()先是DoFile(“tolua.lua”),而tolua.lua中require了所有tolua重写的一些Unity性能有问题的类型,例如Vector3、Vector2、Bound等。接着在C#缓存了这些类型的一些lua方法。

public void Start()
{
        OpenBaseLuaLibs();
        PackBounds = GetFuncRef("Bounds.New");
        UnpackBounds = GetFuncRef("Bounds.Get");
        PackRay = GetFuncRef("Ray.New");
        UnpackRay = GetFuncRef("Ray.Get");
        PackRaycastHit = GetFuncRef("RaycastHit.New");
        PackTouch = GetFuncRef("Touch.New");
    }

 

    // LuaState.cs
    void OpenBaseLuaLibs()
    {
        DoFile("tolua.lua");            //tolua table名字已经存在了,不能用require
        LuaUnityLibs.OpenLuaLibs(L);
    }

 

-- tolua.lua
require "misc.functions"
Mathf       = require "UnityEngine.Mathf"
Vector3     = require "UnityEngine.Vector3"
Quaternion  = require "UnityEngine.Quaternion"
Vector2     = require "UnityEngine.Vector2"
Vector4     = require "UnityEngine.Vector4"
Color       = require "UnityEngine.Color"
Ray         = require "UnityEngine.Ray"
Bounds      = require "UnityEngine.Bounds"
RaycastHit  = require "UnityEngine.RaycastHit"
Touch       = require "UnityEngine.Touch"
LayerMask   = require "UnityEngine.LayerMask"
Plane       = require "UnityEngine.Plane"
Time        = reimport "UnityEngine.Time"

list        = require "list"
utf8        = require "misc.utf8"

require "event"
require "typeof"
require "slot"
require "System.Timer"
require "System.coroutine"
require "System.ValueType"
require "System.Reflection.BindingFlags"

 

这部分先了解下即可。

 

启动3:LuaBinder.Bind(LuaState L)

LuaBinder.cs这个文件是ToLua导出自动生成的,而Bind函数是向lua虚拟机注册所有的wrap类。 
举例来说,LuaBinder.Bind可能如下:

// LuaBinder.cs
public static class LuaBinder
{
    public static void Bind(LuaState L)
    {
        float t = Time.realtimeSinceStartup;
        L.BeginModule(null);
        LuaInterface_DebuggerWrap.Register(L);
        L.BeginModule("UnityEngine");
        UnityEngine_ComponentWrap.Register(L);
        UnityEngine_TransformWrap.Register(L);
        UnityEngine_MaterialWrap.Register(L);
        UnityEngine_LightWrap.Register(L);
        UnityEngine_CameraWrap.Register(L);
        UnityEngine_AudioSourceWrap.Register(L);
        UnityEngine_BehaviourWrap.Register(L);  // behaviour
        L.EndModule();
        L.EndModule();
        ……
    }
}

 

  这里BeginModule可以理解成命名空间,在lua中以table形式实现。BeginModule(null)代表当前命名空间是global全局域,而BeginModule(“UnityEngine”)则代表后面都注册到_G[“UnityEngine”]表中。
  我们打开一个比较基础的UnityEngine类Behaviour的wrap文件UnityEngine_BehaviourWrap.cs查看。刚才LuaBinder中就调用了他的Register方法。

public class UnityEngine_BehaviourWrap
{
    public static void Register(LuaState L)
    {
        L.BeginClass(typeof(UnityEngine.Behaviour), typeof(UnityEngine.Component));
        L.RegFunction("New", _CreateUnityEngine_Behaviour);
        L.RegFunction("__eq", op_Equality);
        L.RegFunction("__tostring", ToLua.op_ToString);
        L.RegVar("enabled", get_enabled, set_enabled);
        L.RegVar("isActiveAndEnabled", get_isActiveAndEnabled, null);
        L.EndClass();
    }
}

 

  这里BeginClass类似BeginModule,但实际功能比BeginModule更加复杂,并且传递了第二个可选的参数,代表着class的基类,在c层以metatable的形式实现对c#继承的支持,暂时不必细究。RegFunction是向该class注册一个方法,RegVar是向该class注册一个属性,即get和set方法。
  一个RegFunction和RegVar例子代码如下:

//L.RegFunction("New", _CreateUnityEngine_Behaviour);
static int _CreateUnityEngine_Behaviour(IntPtr L)
{
    try
    {
        int count = LuaDLL.lua_gettop(L); // 取出参数个数
        if (count == 0)
        {
            UnityEngine.Behaviour obj = new UnityEngine.Behaviour();
            ToLua.Push(L, obj);  // 返回值放到栈上
            return 1;  // 有1个返回值
        }
        else
        {
            return LuaDLL.luaL_throw(L, "xxxerror"); // 异常
        }
    }
    catch(Exception e)
    {
        return LuaDLL.toluaL_exception(L, e); // 异常
    }
}

// L.RegVar("enabled", get_enabled, set_enabled);
static int get_enabled(IntPtr L)
{
    object o = null;

    try
    {
        o = ToLua.ToObject(L, 1);  // 从栈上取出一个对象,此时应为UnityEngine.Behaviour
        UnityEngine.Behaviour obj = (UnityEngine.Behaviour)o;
        bool ret = obj.enabled; // 取出该对象上的enabled值
        LuaDLL.lua_pushboolean(L, ret); // 将值放到栈上
        return 1; // 返回了1个参数
    }
    catch(Exception e)
    {
        return LuaDLL.toluaL_exception(L, e, o == null ? "defaultException" : e.Message);
    }
}

 

  至此,ToLua变形结束,已经可以愉快地与Unity进行混合编程。但在实际项目中,还需要把项目中我们写的lua文件进行加载,可以使用LuaState.DoFile、LuaState.DoString,也可以在lua文件中进行require。但无论何种方式,都要格外注意搜索路径,如果使用LuaState中的DoFile和DoString方法,要调用相应的LuaState.AddSearchPath(“path”)加入进去。而在真机中,由于Android和ios路径问题,默认的tolua.lua是require不到它所需的文件的。需要根据自己的项目有一定的调整。


转载链接:https://blog.csdn.net/lodypig/article/details/60160020

 

标签:LuaState,UnityEngine,流程,require,lua,源码,ToLua,tolua
来源: https://www.cnblogs.com/wodehao0808/p/15753131.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有