tolua源码分析(八)lua扩展继承C#类

news2024/12/25 12:26:34

tolua源码分析(八)lua扩展继承C#类

上一节我们阐述了lua调用带out参数的C#函数机制,本节我们来看下lua层是如何扩展C#类的。这次的例子在example 17,主要都是lua代码:

LuaTransform = 
{                          
}                                                   

function LuaTransform.Extend(u)         
    local t = {}                        
    local _position = u.position      
    tolua.setpeer(u, t)     

    t.__index = t
    local get = tolua.initget(t)
    local set = tolua.initset(t)   

    local _base = u.base            

    --重写同名属性获取        
    get.position = function(self)                              
        return _position                
    end            

    --重写同名属性设置
    set.position = function(self, v)                 	                                            
        if _position ~= v then         
            _position = v                    
            _base.position = v                                                                      	            
        end
    end

    --重写同名函数
    function t:Translate(...)            
        print('child Translate')
        _base:Translate(...)                   
    end    
                   
    return u
end


--既保证支持继承函数,又支持go.transform == transform 这样的比较
function Test(node)        
    local v = Vector3.one           
    local transform = LuaTransform.Extend(node)                                                         

    local t = os.clock()            
    for i = 1, 200000 do
        transform.position = transform.position
    end
    print('LuaTransform get set cost', os.clock() - t)

    transform:Translate(1,1,1)                                                                     
                
    local child = transform:Find('child')
    print('child is: ', tostring(child))
    
    if child.parent == transform then            
        print('LuaTransform compare to userdata transform is ok')
    end

    transform.xyz = 123
    transform.xyz = 456
    print('extern field xyz is: '.. transform.xyz)
end

C#层负责传一个transform对象给Test方法调用,运行的结果如下:

tolua源码分析(八)1

可以发现,lua层在Extend中重写的同名属性和方法都被调用到了,甚至最后给transform新增的字段xyz也生效了。这是怎么做到的呢?我们来细细研究下函数LuaTransform.Extend

首先,可以看到第8行有一句:tolua.setpeer(u, t),这句话会调用到tolua_bnd_setpeer

static int tolua_bnd_setpeer(lua_State *L) 
{
    // stack: userdata, table
    if (!lua_isuserdata(L, -2)) 
    {
        return luaL_error(L, "Invalid argument #1 to setpeer: userdata expected.");        
    }

    if (lua_isnil(L, 2)) 
    {
        lua_pop(L, 1);
        lua_pushvalue(L, TOLUA_NOPEER);
        lua_setfenv(L, -2);
    }        
    else
    {
        lua_pushvalue(L, 2);                //stack: u p p
        lua_setfenv(L, -3);                 //stack: u p
        lua_newtable(L);                    //stack: u p vt        
        
        lua_pushlightuserdata(L, &vptr);    
        lua_pushvalue(L, 1);
        lua_rawset(L, -3);
        //lua_pushvalue(L, 1);
        //lua_rawseti(L, -2, 1);

        lua_getref(L, LUA_RIDX_VPTR);       //stack: u p vt mt
        lua_setmetatable(L, -2);            //stack: u p vt

        lua_pushstring(L, "base");          //stack: u p vt "base"
        lua_pushvalue(L, -2);               //stack: u p vt "base" vt
        lua_rawset(L, 2);                   //stack: u p vt    
        lua_pop(L, 1);
    }

    return 0;
};

这个函数接收两个参数,第一个是userdata,表示C#类的对象,第二个是table,表示用来扩展该对象的lua数据结构。如果table为空,则userdata没有扩展table,那么就为它设置一个空的env table,这里TOLUA_NOPEER就是registry table。其实到这,我们应该就已经猜到,tolua是使用env table的方式,把lua的table绑定到userdata上,从而实现对userdata的扩展。

由于扩展后的userdata它的类型依旧是userdata,我们希望能够通过它灵活访问到扩展前和扩展后的内容,所以这里又引入了一个vptr的table。它用一个light userdata的key,绑定了对应的userdata,以及名为LUA_RIDX_VPTR的metatable。最后,我们设置env table的base域为这个vptr。

第11-12行就是获取tolua里绑定C# get/set属性的table,将其绑定到新的env table上。通过修改get/set table,就可以改变C# get/set属性的行为了。听上去似乎云里雾里,我们不如直接来看下执行过这些步骤后,访问C# userdata的逻辑会发生哪些改变。在例子中,我们重写了transform的postion属性,和translate方法,那么在lua层实际调用时,会触发到class_index_event函数中的如下逻辑:

lua_getfenv(L,1);

if (!lua_rawequal(L, -1, TOLUA_NOPEER))     // stack: t k env
{
    while (lua_istable(L, -1))                       // stack: t k v mt 
    {      
        lua_pushvalue(L, 2); 
        lua_rawget(L, -2);
    
        if (!lua_isnil(L, -1))
        {                    
            return 1;
        }

        lua_pop(L, 1);
        lua_pushlightuserdata(L, &gettag);          
        lua_rawget(L, -2);                      //stack: obj key env tget
    
        if (lua_istable(L, -1))
        {                    
            lua_pushvalue(L, 2);                //stack: obj key env tget key
            lua_rawget(L, -2);                  //stack: obj key env tget func 

            if (lua_isfunction(L, -1))
            {                        
                lua_pushvalue(L, 1);
                lua_call(L, 1, 1);
                return 1;
            }    

            lua_pop(L, 1);                 
        }

        lua_pop(L, 1); 

        if (lua_getmetatable(L, -1) == 0)               // stack: t k v mt mt
        {
            lua_pushnil(L);
        }

        lua_remove(L, -2);                              // stack: t k v mt
    }        
}

如果userdata上绑定的env table是有效table,那么优先访问该table,这很好理解,毕竟子类如果重写了父类的方法,调用时应当优先调用子类的实现。对于translate方法,就是直接去env table中获取key为translate的value;对于position属性,就是走get属性的table,获取到对应的value,它应该是一个function,调用之后返回的值为get返回的属性。当然,如果访问的是子类没有重写过的父类方法,则这里的判断都不会命中,会进而获取userdata的metatable,也就是前面我们所说过的class table,进行递归查找。

在子类的实现中,我们往往需要直接调用父类的方法。tolua这里类似,可以通过访问userdata的base域,由上文可知,这里返回的会是一个vptr的table。对它访问会触发它的metatable的__index元方法,也就是vptr_index_event函数:

static int vptr_index_event(lua_State *L)
{    
    lua_pushlightuserdata(L, &vptr);
    lua_rawget(L, 1);                                   // stack: t key u
    lua_replace(L, 1);                                  // stack: u key
    lua_pushvalue(L, 1);                                // stack: u key u

    while (lua_getmetatable(L, -1) != 0)
    {
        lua_remove(L, -2);                              // stack: u key mt
        lua_pushvalue(L, 2);                            // stack: u key mt key
        lua_rawget(L, -2);                              // stack: u key mt value        

        if (!lua_isnil(L, -1))
        {
            return 1;
        }
        
        lua_pop(L, 1);        
        lua_pushlightuserdata(L, &gettag);          
        lua_rawget(L, -2);                              //stack: u key mt tget

        if (lua_istable(L, -1))
        {
            lua_pushvalue(L, 2);                        //stack: obj key mt tget key
            lua_rawget(L, -2);                          //stack: obj key mt tget value 

            if (lua_isfunction(L, -1))
            {
                lua_pushvalue(L, 1);
                lua_call(L, 1, 1);
                return 1;
            }
        }

        lua_settop(L, 3);
    }
}

访问vptr table的效果其实应当于直接访问扩展之前的userdata效果一样。所以这里有一个比较trick的实现,就是将vptr table中绑定过的userdata取出,再对userdata进行访问,即对它的metatable进行递归查找。之所以对所有的vptr table都设置同样一个这样的metatable,其原因是它们的逻辑是完全相同的,唯一不同就是各自绑定的userdata不同,因此只需要在metatable的元方法中,把vptr table绑定的userdata传进去就行了,没必要为每个vptr table都设置一个单独的metatable。这里的实现是比较巧妙的。

在例子中,我们还看到对扩展后的userdata进行判等的操作。由于扩展后的userdata依旧是userdata,它自身的value其实是不变的,所以这里判等一定是成立的。例子的最后展示了对userdata设置各种value的操作,如果是扩展前的C#类userdata,这里就会报错了,因为事先注册的set table中没有这些key。而扩展后就不一样了,这里的各种set,最终会往env table中进行set,而env table只是一个普通的lua table,当然就没有问题了。可以看到,对userdata进行扩展,能够极大地提高它使用的灵活性。

下一节我们将讨论lua层如何使用反射调用没有wrap过的C#函数。

如果你觉得我的文章有帮助,欢迎关注我的微信公众号 我是真的想做游戏啊

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/656540.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

1.pixi.js编写的塔防游戏(类似保卫萝卜)-设计逻辑

游戏说明 一个用pixi.js编写的h5塔防游戏,可以用electron打包为exe,支持移动端,也可以用webview控件打包为app在移动端使用 环境说明 cnpm6.2.0 npm6.14.13 node12.22.7 npminstall3.28.0 yarn1.22.10 npm config list electron_mirr…

1.2 聚合工程与springboot整合

步骤1&#xff1a; 在顶级父工程中添加springboot相关的pom配置 <!--springboot父级依赖&#xff0c;表示是一个是springboot项目 --> <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifa…

vue3(基于vite)项目初始基本配置

简介 一个好的项目开始搭建总是需要配置许多初始化配置&#xff0c;比如eslint语法检验、prettier代码格式统一、husky做commit拦截等等&#xff0c;本文从零开始带你一步步搭建一个完整的项目配置&#xff0c;熟悉之后下次直接拿来即用 环境准备 node v16以上pnpm 8.0.0 1.新…

4.3.3 info page

在所有的Unix Like系统当中&#xff0c;都可以利用 man 来查询指令或者是相关文件的用法&#xff1b; 但是&#xff0c;在Linux里面则又额外提供了一种线上求助的方法&#xff0c;那就是利用info。 基本上&#xff0c;info与man的用途其实差不多&#xff0c;都是用来查询指令的…

看完就会,从抓包到接口测试的全过程解析!

一、为什么抓包 1、从功能测试角度 通过抓包查看隐藏字段 Web 表单中会有很多隐藏的字段&#xff0c;这些隐藏字段一般都有一些特殊的用途&#xff0c;比如收集用户的数据&#xff0c;预防 CRSF 攻击&#xff0c;防网络爬虫&#xff0c;以及一些其他用途。这些隐藏字段在界面…

数据库大题

(计算题&#xff0c;20分) 设有两个关系R和S,求① R ∪ S R \cup S R∪S;② R − S R - S R−S;③ R S R \times S RS;④ ∏ C , A ( R ) \prod_{C,A}(R) ∏C,A​(R);⑤ σ B > ′ 4 ′ ( R ) \sigma_{B>4}(R) σB>′4′​(R) 关系R关系S (简答题&#xff0c;10…

Telnet是什么协议?如何使用?

作者&#xff1a;Insist-- 个人主页&#xff1a;insist--个人主页 作者会持续更新网络知识和python基础知识&#xff0c;期待你的关注 目录 ​编辑 一、Telnet是什么协议&#xff1f; 二、Telnet用在哪&#xff1f; 三、Telnet协议的优点和缺点 ​编辑 1、优点 ①简单易…

Microservice架构模式简介

在2014年&#xff0c;Sam Newman&#xff0c;Martin Fowler在ThoughtWorks的一位同事&#xff0c;出版了一本新书《Building Microservices》。该书描述了如何按照Microservice架构模式设计及搭建一个具有良好扩展性并可持续开发的系统。除此之外&#xff0c;该书还将基于该模式…

pygame光栅化三角形 用像素填充三角形

参考文章 DirectX C 3D编程基础 5 [三角形光栅化] 自制中字 这次用像素填充三角形&#xff0c;是为了下次用图片做纹理填充三角形做准备。 前面的文章说了根据直线方程求出已知两点间任意一点的坐标&#xff0c;现在来试试用像素填充三角形。 首先画一个三角形 pygame.draw.…

whisperX 语音识别本地部署

WhisperX 是一个优秀的开源Python语音识别库。 下面记录Windows10系统下部署Whisper 1、在操作系统中安装 Python环境 2、安装 CUDA环境 3、安装Annaconda或Minconda环境 4、下载安装ffmpeg 下载release-builds包&#xff0c;如下图所示 将下载的包解压到你想要的路径&#xf…

华为OD机试之Boss分销提成计算(boss的收入)(Java源码)

Boss分销提成计算&#xff08;boss的收入&#xff09; 题目描述 一个XX产品行销总公司&#xff0c;只有一个boss&#xff0c;其有若干一级分销&#xff0c;一级分销又有若干二级分销&#xff0c;每个分销只有唯一的上级分销. 规定&#xff0c;每个月&#xff0c;下级分销需要将…

利用OpenCV做个熊猫表情包吧

有的时候很想把一些有意思的图中的人脸做成熊猫表情&#xff0c;但是由于不太会ps&#xff0c;只能无奈放弃&#xff0c;so sad... 正好最近想了解下opencv的使用&#xff0c;那就先试试做个简单的熊猫表情生成器把~~ 思路就是&#xff0c;工具给两个参数&#xff0c;一个是人…

矩阵卷积之边缘计算

目录 note code test note code // 不扩充边缘的矩阵卷积&#xff0c;输出矩阵和输入矩阵一样大 void MyMatConvolute2(Mat& src, Mat& kernel, Mat& res) {if (src.type() ! CV_8UC1) {return;}if ((kernel.type() ! CV_8UC1) || (kernel.rows ! kernel.cols) …

2023年端午节放假通知

过节、业务两不误&#xff0c;服务“不打烊”。欢迎新老客户前来咨询下单&#xff01;感谢各位一直以来的支持和信赖&#xff0c;你们是易天光通信成长道路上不可或缺的力量&#xff0c;提前祝大家在端午节玩得开心&#xff0c;吃得开心&#xff01; 深圳易天光通信有限公司 …

C#打印控件ReportViewer使用总结(三)

ReportViewer控件设计 在工具箱中添加ReportViewer控件&#xff0c;控件名称为reportViewer1。 2.绑定报表和数据集 /// <summary> /// 绑定报表和数据集 /// </summary> private void DataBing() { //绑定报表 this.reportViewer1.LocalReport.ReportPath Syste…

Java官方笔记8泛型

泛型 为什么需要泛型&#xff1f;generics enable types (classes and interfaces) to be parameters when defining classes, interfaces and methods. 说白了就像Python动态语言的变量&#xff0c;是动态的&#xff0c;可以指向任意类型。 泛型有个好处是不需要类型转换&…

有没有不限制群发数量的软件?

父亲节的由来 父亲节&#xff08;Fathers Day&#xff09;&#xff0c;顾名思义是感恩父亲的节日。 世界上第一个父亲节&#xff0c;1910年诞生于美国。 而中国的父亲节起源要追溯到民国时代。民国三十四年的八月八日&#xff08;1945.8.8&#xff09;&#xff0c;上海文人所…

git-在当前分支做变基(rebase current onto selected)

文章目录 git rebase变基git rebase使用场景git rebase还是git merge?在当前分支做变基使用总结 git rebase变基 初学git&#xff0c;在合并分支上必定会常用到 git merge 语法。git除了使用合并(merge)集成2个分支之间的更改&#xff0c;还有另外一种称为rebase的方法。 gi…

R -- corrplot包

文章目录 常用参数简介method测试部分type参数测试diag参数测试order参数测试 常用参数简介 corrplot包常用参数介绍&#xff1a; corr: 必需参数&#xff0c;表示相关性矩阵&#xff0c;可以是数据框或者矩阵。 method: 表示绘制相关性矩阵的方法&#xff0c;有以下几种取值…

H5语义化标签

语义化标签 HTML5引入了一组语义化标签,这些标签旨在更好地描述网页内容的结构和含义。通过使用这些标签,可以提高网页的可读性、可访问性、搜索引擎优化,以及代码的可维护性。以下是对HTML5语义化标签的详细解释: <header>:用于定义页面或区块的页眉部分,通…