zoukankan      html  css  js  c++  java
  • Ulua对象管理方式

    不管是C++中还是在C#中,在都绕不开一个问题:类对象怎么在Lua中使用的问题,还好Lua提供了Userdata以及ligh Userdata结构类型,通过扩展可以处理这方面的问题。现在的很多框架也大致类似的方式进行处理。

    在前面的一些笔记<Lua 与 C 交互之UserData(4)>中提到过几个使用宿主类对象中的问题:

    1. 数据内存
    2. 生命周期
    3. 数据操作

    那篇文章中给出的案例是《Lua程序设计》中给出的教程,数据放在Lua中,由Lua来管理对象的内存数据,这种方式未必不好,需要优化的是可以在userdata的metatable中增加__gc元方法,去通知宿主语言释放信息,避免宿主语言中对空指针的使用。

    Ulua对象管理方式:Ulua采用对象数据在C#层,userdata值记录对象在c#数组中index,并在metatable提供查找对象、访问对象方法的能力以及释放对象时清除c#数据的能力。当然不同的类型Ulua提供的metatable也不尽相同,暂且已class_object来了解这个过程。

    支持对象方法访问

    在1.03版本以后,ULua中增加了很多其他类型的支持,比如enum、array等,对于每种类型,都会提供对应的metatable提供访问类元素的能力,比如Object_class。当然metatable也可以用作类型的判断。

     private void createIndexingMetaFunction(IntPtr luaState)
     {
         LuaDLL.lua_pushstring(luaState, "luaNet_indexfunction");
         LuaDLL.luaL_dostring(luaState, MetaFunctions.luaIndexFunction);
         //LuaDLL.lua_pushstdcallcfunction(luaState,indexFunction);
         LuaDLL.lua_rawset(luaState, (int)LuaIndexes.LUA_REGISTRYINDEX);
     }
    
    //创建物体时
    private void pushNewObject(IntPtr luaState, object o, int index, string metatable)
    {
    	...
        if (metatable == "luaNet_metatable")
        {
            // Gets or creates the metatable for the object's type
            //string meta = t.AssemblyQualifiedName
            //LuaDLL.luaL_getmetatable(luaState, meta);
            Type t = o.GetType();
            PushMetaTable(luaState, o.GetType());
    
            if (LuaDLL.lua_isnil(luaState, -1))
            {
                string meta = t.AssemblyQualifiedName;
                Debugger.LogWarning("Create not wrap ulua type:" + meta);
                LuaDLL.lua_settop(luaState, -2);
                LuaDLL.luaL_newmetatable(luaState, meta);
                LuaDLL.lua_pushstring(luaState, "cache");
                LuaDLL.lua_newtable(luaState);
                LuaDLL.lua_rawset(luaState, -3);
                LuaDLL.lua_pushlightuserdata(luaState, LuaDLL.luanet_gettag());
                LuaDLL.lua_pushnumber(luaState, 1);
                LuaDLL.lua_rawset(luaState, -3);
                LuaDLL.lua_pushstring(luaState, "__index");
                LuaDLL.lua_pushstring(luaState, "luaNet_indexfunction");
                LuaDLL.lua_rawget(luaState, (int)LuaIndexes.LUA_REGISTRYINDEX);
                LuaDLL.lua_rawset(luaState, -3);
                LuaDLL.lua_pushstring(luaState, "__gc");
                LuaDLL.lua_pushstdcallcfunction(luaState, metaFunctions.gcFunction);
                LuaDLL.lua_rawset(luaState, -3);
                LuaDLL.lua_pushstring(luaState, "__tostring");
                LuaDLL.lua_pushstdcallcfunction(luaState, metaFunctions.toStringFunction);
                LuaDLL.lua_rawset(luaState, -3);
                LuaDLL.lua_pushstring(luaState, "__newindex");
                LuaDLL.lua_pushstdcallcfunction(luaState, metaFunctions.newindexFunction);
                LuaDLL.lua_rawset(luaState, -3);
            }
        }
        else
        {
            LuaDLL.luaL_getmetatable(luaState, metatable);
        }
        ...
    }
    

    在添加新对象的时候,会创建的Metatable,并设置元方法。通过 _ _ index实现对类对象的访问,当然这里是通过反射机制来实现的。
    通过Wraper全局注册的方式并不需要这里的_ _index的访问了。Ulua经过几个版本的更新,代码相当的混乱了。到 toLuaC#中其实__Index已经删除。

    对象管理

    对象列表
    C#维护一个weak table,以数组index为key记录对象userdata数据。

     private void createLuaObjectList(IntPtr luaState)
     {
         LuaDLL.lua_pushstring(luaState, "luaNet_objects");
         LuaDLL.lua_newtable(luaState);
         LuaDLL.lua_pushvalue(luaState, -1);
         weakTableRef = LuaDLL.luaL_ref(luaState, LuaIndexes.LUA_REGISTRYINDEX);
         LuaDLL.lua_pushvalue(luaState, -1);
         LuaDLL.lua_setmetatable(luaState, -2);
         LuaDLL.lua_pushstring(luaState, "__mode");
         LuaDLL.lua_pushstring(luaState, "v");
         LuaDLL.lua_settable(luaState, -3);
         //LuaDLL.lua_setmetatable(luaState,-2);
         LuaDLL.lua_settable(luaState, (int)LuaIndexes.LUA_REGISTRYINDEX);
     }
    

    增加对象
    在添加物体时 会产生一个index作为该object唯一的参数保存在一张C#列表中,并将这个Indx和userdata数据放在一个weaktable(weakTableRef为LuaIndexes.LUA_REGISTRYINDEX表中索引)。

    private void pushNewObject(IntPtr luaState, object o, int index, string metatable)
    {
        LuaDLL.lua_getref(luaState, weakTableRef);
        LuaDLL.luanet_newudata(luaState, index);
        ...
        //创建metatable,并放在入栈
        ...
        LuaDLL.lua_setmetatable(luaState, -2);
        LuaDLL.lua_pushvalue(luaState, -1);
        LuaDLL.lua_rawseti(luaState, -3, index);
        LuaDLL.lua_remove(luaState, -2);
        ...
    }
    //返回新的Index
    int addObject(object obj)
    {
    	// New object: inserts it in the list
    	int index = nextObj++;
    	objects[index] = obj;
    	objectsBackMap[obj] = index;
    
    	return index;
    }
    

    获取对象
    顺便提一下UserData中存储的是index值,了解下luanet_newudata接口的实现就可以明白。

    LUALIB_API void luanet_newudata(lua_State *L,int val) 
    {
      int* pointer=(int*)lua_newuserdata(L,sizeof(int));
      *pointer=val;
    }
    

    那怎么获取对象呢?既然知道userdata中存储的是index的数据,那么就可以在获取userdata之后,直接在C#中查表获得对象。

    LUALIB_API int luanet_rawnetobj(lua_State *L,int index) 
    {
      int *udata = lua_touserdata(L,index);
      if(udata!=NULL) return *udata;
      return -1;
    }
    
    //C#中接口
     internal object getRawNetObject(IntPtr luaState, int index)
     {
         int udata = LuaDLL.luanet_rawnetobj(luaState, index);
         object obj = null;
         objects.TryGetValue(udata, out obj);
         return obj;
     }
    

    删除对象
    既然对象保存在C#层,那Lua中如果释放了该对象,C#层如何释放呢?还是通过metatable中元方法,[Lua5.1中userdata支持_ GC方法][3],对象被Lua回收期释放的时候调用这个方法。C#层中将对象移除。

    internal void collectObject(int udata)
     {
         object o;
         bool found = objects.TryGetValue(udata, out o);
         // The other variant of collectObject might have gotten here first, in that case we will silently ignore the missing entry
         if (found)
         {
             objects.Remove(udata);
             if (o != null && !o.GetType().IsValueType)
             {
                 objectsBackMap.Remove(o);
             }
         }
     }
    

    对于增加新物体时,如果发现这个对象在C#缓冲列表中存在,但是在Lua中不存在,那就可以删除了。这里有使用到weak table,也就是如果userdata不在被引用,就会被Lua垃圾收集器回收。

     public void pushObject(IntPtr luaState, object o, string metatable)
     {
         if (o == null)
         {
             LuaDLL.lua_pushnil(luaState);
             return;
         }
    
         int index = -1;
         // Object already in the list of Lua objects? Push the stored reference.
         bool beValueType = o.GetType().IsValueType;
         if (!beValueType && objectsBackMap.TryGetValue(o, out index))
         {
             if (LuaDLL.tolua_pushudata(luaState, weakTableRef, index))
             {
                 return;
             }
    
    // Note: starting with lua5.1 the garbage collector may remove weak reference items (such as our luaNet_objects values) when the initial GC sweepoccurs, but the actual call of the __gc finalizer for that object may not happen until a little while later.  During that window we might call this routine and find the element missing from luaNet_objects, but collectObject() has not yet been called.  In that case, we go ahead and call collect object here
    // did we find a non nil object in our table? if not, we need to call collect object Remove from both our tables and fall out to get a new ID
             collectObject(o, index);
         }
         index = addObject(o, beValueType);
         pushNewObject(luaState, o, index, metatable);
     }
    

    存在的问题

    1. 如果Lua中一直持有对象引用,就不会释放这个对象,造成无法回收
    2. 物体不使用的时候直接Destroy掉,会增加GC消耗,增加pool的会更合理一点
      在后续版本中对这些情况也做了对应的优化。

    [2]:http://www.cnblogs.com/zsb517/p/6423342.html “Lua 基础之Weak Table(5)”
    [3]:http://www.cnblogs.com/zsb517/p/6423472.html "Lua基础之MetaTable(6)"

  • 相关阅读:
    (01)Docker简介
    Gym-101242B:Branch Assignment(最短路,四边形不等式优化DP)
    2019牛客暑期多校训练营(第三场)G: Removing Stones(启发式分治)
    POJ
    高维前缀和
    HDU
    BZOJ
    HDU
    POJ
    Gym-100648B: Hie with the Pie(状态DP)
  • 原文地址:https://www.cnblogs.com/zsb517/p/6428826.html
Copyright © 2011-2022 走看看