Lua的使用心得: 数据定义和过程定义(Lua在程序中的数据定义和过程定义的界定原则的研究)

 

Lua在程序中的数据定义和过程定义的界定原则的研究


引言

作为宿主语言的衍生,Lua无论从数据对象的填充,还是处理过程的定制,都提供了很好的支持。甚至我们可以将全部的宿主语言都搬到Lua里来写。在这样大 的灵活度下,如何界定什么样的函数需要导出到Lua,如何对数据对象定义,或者说使用Lua的基本思路是什么,时常让刚学会Lua的人迷惑。本文使用一个 实际例子来讲述一个C++系统和Lua结合的演变过程、思路,并比较各个方案之间的优劣,提供一个使用Lua的参考思路。


找出需要定制的地方

在我们的游戏引擎中有一个 renderroom 系统,就是用来实时渲染一个对象到贴图的轻量系统。整个系统的静态结构就在这里略了,重点在于RenderRoom的定制。当你需要渲染一个角色的时候,需要确定
摄象机的位置、朝向
场景模型、特效

最开始的时候也就考虑到了这3点。因为我们那个时候需要渲染生物只有2种需求,渲染头和渲染全身,因此在C++里定义了以下类型

struct RenderType
{
        enum T
        {
                Head,
                Body,
        };

};
并写了一个处理函数:
void refreshTexture(RenderType::T type)
{
        switch( type)
        {
         case RenderType::Head:
                设置摄象机等参数;
                break;
         case RenderType::Head:
                设置摄象机等参数;
                break;
        }
}


但是在这之后发现,渲染人型生物和渲染爬行类生物的时候, Head的参数根本就不同。
为了迅速解决问题,添加了新的Head类型。
这当然是个非常临时的方法,所以不久之后我们就碰到生物体型过多,参数类型过多的问题。于是“可配置”的需求就变的强烈了。这个时候开始考虑利用Lua作为数据定义文件。我尝试了以下几种方式。

一、在Lua中定义数据文件结构,将数据对象返回给CPP进行解析。

// Lua
local data={
cameraPos = {x,y,z} // 这是一个原生结构,如果你将 Vector3 之类的结构导出到Lua了,这里就可以省不少事情
cameraDir = {x,这里就可以省不少事情
scene = "RenderRoomScene1.scene"
};

parseRenderRoomData(data);  ---在C++中定义的函数. 导出到Lua
data=nil;

// CPP
parseRenderRoomData(lua_State* L)
{
        // 解析表;
        // 使用 lua_next 取得各个元素,按照Lua中定义的数据格式进行解析
};


这个方案是不好的,原因在于 parseRenderRoomData 对数据的解析过程受制于 data的定义,而data中的数据经常变化,甚至结构也经常变化,所以需要在 parseRenderRoomData 函数中编写大量的异常处理代码防止数据文件损坏的时候程序不崩溃,或者说引起Lua的栈不平衡.
这个方案唯一的优点可能就是好理解吧:Lua提供数据对象,CPP解析并创建实例对象.


二、CPP中定义数据文件结构,创建对象,Lua填充数据,将数据对象返回给CPP进行解析。

这个方案的代码静态结构大致是这样:


//CPP
struct RenderRoomData
{
        // 这是一个原生结构,这里就可以省不少事情
        float cameraPos_x;
        float cameraPos_y;
        float cameraPos_z;
        float cameraDir_x;
        float cameraDir_y;
        float cameraDir_z;
        std::string sceneFile;
};

RenderRoomData* createRenderRoomDate()
{
        return new RenderRoomData;
}

parseRenderRoomData(lua_State* L)
{
        // 同上,但是是按照 RenderRoomData 的定义进行解析.
}

// Lua
local data= createRenderRoomDate()
data.cameraPos_x;
data.cameraPos_y;
data.cameraPos_z;
data.cameraDir_x;
data.cameraDir_y;
data.cameraDir_z;
data.sceneFile = "RenderRoomScene1.scene"
parseRenderRoomData(data);
data=nil;

这个方案从核心上来说和方案一一致,就是Lua提供数据. 但是在数据协议方面则做了非常大的改进,因为是使用CPP定义的对象,所以解析的过程就可以确定下来.这样可以将错误处理代码省略.但这只是理由之一,最 大的理由在于Lua文件一般是由编辑器或策划来负责维护,现在数据格式由程序定义好,则会大大减少数据文件出错几率.并且数据定义这样的事情也就在一开始 变动频繁,当稳定后基本上是不变的.考虑到变动的成本,方案二远胜于方案一.

在数据定义方面,我会选择方案二,因为实际情况是如果你采用方案一,写错误处理的时间会根据数据文件的复杂程度急剧增长,最终从各个方面都会劣于方案一。 但是如果是在系统设计的初期,几乎没有任何错误处理,所以方案一也不失为一种方便的原型模型。这个需要参考最终代码的质量标准。

(我哭死了,写了1个多小时的内容被我自己覆盖了...以后再也不用txt写东西了,用vs)
新的演化

如果需求能一次到位,写程序也就不用这样累了,所以"需求总是变化的"这句真理永远适用。RenderRoom的核心思路是为了将一个对象渲染到贴图,在一些引擎里这个系统叫"化身". 根据使用场合的不同,我们会希望贴图有的需要背景使用透明色,比如头像,有的又不希望它透明,比如装备栏里的人物角色,并且在一些时候你还想定制Fog,Bloom等参数. 但是这样的配置数据也就只有有限的几种,而像摄象机位置、朝向这样的信息是需要每怪物单独设置的,这样RenderRoomData里就需要包含2种不同类型的数据: 每对象数据 和 每类型数据,两者的数量级比例大致是1000:10. 一般对于这样的数据拆分的常见手段是使用ID或者说句柄. 在这里首先定义一个新的数据结构:
struct RenderParameters
{
        // 渲染相关的参数定义
};
不在这里定义ID的理由,随后说明.


对于这个新结构的使用方法,有以下几种方案:
一、数据定义冗余策略.

// CPP
在 struct RenderRoomData 中增加一个成员变量
struct RenderRoomData
{
        ...
        RenderParameters renderParameters;
};


// Lua

local data= createRenderRoomDate()
...
data.renderParameters.propory1 = a;
data.renderParameters.propory2 = b;
data.renderParameters.propory3 = c;

...

对于数据冗余策略,ID是没有必要的,所以在RenderParameters的原型定义里没有定义ID。

二、使用ID检索策略

// CPP

在 struct RenderRoomData 中增加一个成员变量

struct RenderParameters
{
        unsigned int typeID;
        // 渲染相关的参数定义
};

在 struct RenderRoomData 中增加一个成员变量
struct RenderRoomData
{
        ...
        unsigned int renderParametersID;
};

现在你可以使用任何喜欢的检索方式对RenderParameters对象进行创建和检索,比如:

1、使用在之前讨论数据定义的方式中提到的方案二,在CPP中创建,Lua中填充,并在CPP中检索他们。

2、利用Lua的全局对象,对其进行填充和检索。
3、定义Lua函数,利用ID将冗余数据自动填充。这个方法是方案一和方案二的结合。

//Lua

--相信看到这里,各位看官都有自己的想法了,我就不叨叨了

三、定义Lua函数对象

使用这个方法有先决条件:如果你将 refreshTexture 中调用的具体干活的函数导出了的话,就可以使用这个方法。暂时将这些功能函数称其为“干活函数”。

分析我们定义RenderParameters的最初目的就可以得知,实际上我们就是在为干活函数搜集参数,并从Lua中传递到CPP中给它们使用。因此如果我们能在Lua中直接调用干活函数的话,就可以使用更加简单的方式来处理“RenderRoom的定制”这个终极目标。

// CPP
// 实现一个能调用Lua函数的函数,如
template<typename Par1,typename Par2,typename Par3>

bool callLuaFunction(const char* functionName,Par1 param1,Par2 param2,Par3 param3);
这个函数需要能把 Lua对象向 ParX 类型的对象进行转化,实现方式很多,各位自行拿捏.
定义参数的理由随后说明.

// Lua

--定义一个全局表
RenderRoomBulider = {}
--添加具体Builder
RenderRoomBulider["头像"] = function(cameraPos,cameraDir,viewport) -- 视口对象.因为Fog,Bloom等参数是每视口设置的.
        SetCameraPos(cameraPos);

        SetCameraPos(cameraPos);
        CreateSceneStage("RenderRoomScene1.scene");
        SetFogDistance(...);
        SetFogColor(...);

        SetBloom();
end;
RenderRoomBulider["装备栏"] = function(cameraPos,viewport)
        SetCameraPos(cameraPos);

        SetBloom();
end;

....

最后在RenderRoom初始化的地方写如下代码:

std::string builderTypeName = "RenderRoomBulider[头像]";
Vector3 cameraPos = 具体怪物的CameraPos;
Vector3 cameraDir = 具体怪物的CameraDir;
Viewport* viewport = RenderRoom关联的视口;
callLuaFunction( builderTypeName.c_str(),cameraPos,viewport);

最后的问题在于具体怪物的Camera属性记录在哪呢,我相信每个游戏都有一个怪物数据表吧,就记录在那里吧.

Camera属性是每怪物的,RenderParameter是每应用场景的,我们通过怪物表获取Camera属性,通过Lua获取RenderParameter属性,最终将RenderRoom的定制从程序中释放出来了.

小结

Lua的特点在于灵活,具体表现就是没有任何限制,但是就象你可以拿 C风格的强制类型转换在C++里写任意对象的转换一样,总是有原则存在其中的,这些原则可以帮助我们写出健壮的、易维护的代码,对于脚本语言更加如此. 这些原则可能来自于程序经理要求,项目规范,个人习惯等等方面,但是最重要的一条就是,要统一原则,并贯彻下去.

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


1.github代码实践源代码是lua脚本语言,下载th之后运行thmain.lua-netTypevgg-savevgg_cifar10/-S0.0001,报错: 试看看安装lua:报错了,参考这篇文章:ubuntu18.04安装lua的步骤以及出现的问题_weixin_41355132的博客-CSDN博客问题解决,安装成功:情况并没有好转,出现相
此文为搬运帖,原帖地址https://www.cnblogs.com/zwywilliam/p/5999924.html前言在看了uwa之前发布的《Unity项目常见Lua解决方案性能比较》,决定动手写一篇关于lua+unity方案的性能优化文。整合lua是目前最强大的unity热更新方案,毕竟这是唯一可以支持ios热更新的办法。然而作
Rime输入法通过定义lua文件,可以实现获取当前时间日期的功能。1.TIMERime是一款可以高度自定义的输入法,相关教程可以查看往期文章,关于时间获取是指输入一个指定关键字,输出当前时间,效果如下(我定义了time关键字):实现如下:①在用户文件夹中新建一个rime.lua文件加入如下代码 ti
localfunctiongenerate_action(params)localscale_action=cc.ScaleTo:create(params.time,params.scale_x,params.scale_y)localfade_action=cc.FadeIn:create(params.time)returncc.Spawn:create(scale_action,fade_action)end
2022年1月11日13:57:45 官方:https://opm.openresty.org/官方文档:https://opm.openresty.org/docs#table-of-contents为什么建议使用opm不建议使用luarocks?http://openresty.org/cn/using-luarocks.html官方解释:请注意!LuaRocks并不是OpenResty官方推荐的装包方式。LuaRoc
在Lua中的table(表),就像c#中的HashMap(哈希表),key和value一一对应。元表:table的一个操作的拓展,里面包含关联了对应的方法,元方法就是其中一个。元方法:当你通过键来访问table的时候,如果这个键没有值,那么Lua就会寻找该table的metatable(假定有metatable)中的__index键。如果__inde
表排序:table.sort(list[,comp])参数list:指定表,可选参数comp:排序函数,无参数时通常按升序排序。排序函数针对表中连续的序列,其间不可以存在空洞或nil,排序函数需要两个形参(对应表中每次参加比较的两个数据),需要一个比较两个形参表达式的返回值,不能含有等于关系,例如>=,<=,==。do
一、安装lua环境1.1安装依赖包[root@centos7~]#yuminstallgccreadline-devel1.2下线lua源码包并解压[root@centos7~]#wgethttp://www.lua.org/ftp/lua-5.3.5.tar.gz[root@centos7~]#tarxvflua-5.3.5.tar.gz-C/usr/local/src1.3进行编译[root@centos7~]
官网OpenResty® 是一个基于 Nginx 与Lua的高性能Web平台,其内部集成了大量精良的Lua库、第三方模块以及大多数的依赖项。用于方便地搭建能够处理超高并发、扩展性极高的动态Web应用、Web服务和动态网关。OpenResty® 通过汇聚各种设计精良的 Nginx 模块(主要由
表参考《lua程序设计》可以认为,表是一种动态分配的对象,程序只能操作指向表的引用(或指针)。除此以外,Lua语言不会进行隐藏的拷贝(hiddencopies)或创建新的表--创建表a={}--创建空表k="x"a[k]=10--键“x”值10a[20]="great"--键20值“great”print(a["x"])-->10
https://github.com/galenho/crossover.git一个跨平台的lua游戏服务器开发框架,该框架采用多线程并发来处理消息,开发者只需要调用相应的接口函数并绑定相应的回调函数即可,在逻辑层表现为单线程的开发模式,使开发者易用,易调试,易维护,易扩展,同时拥有快速的响应能力。   框架使用面
参考链接:https://www.runoob.com/lua/lua-metatables.htmlhttps://www.jianshu.com/p/cb945e7073a3 元表是一个table,可以让我们改变table的行为,每个行为有对应的元方法例如,对table进行设置键值,查找键值,运算等,就会触发对应的元方法1--__index:table被访问时,如果找不到这
https://github.com/yuin/gopher-luahttps://github.com/yuin/gopher-lua Lua5.1ReferenceManual-contentshttp://www.lua.org/manual/5.1/ go中使用luapackagemainimport( lua"github.com/yuin/gopher-lua")funcmain(){ l:=lua.NewState() d
编译问题不要留到运行时才跑出来啊。早上9:00-中午3:00,6个小时,服了自己了。 写了一个测试,springboot+redis+lua执行到redisTemplate.execute(redisScript,idList)的时候一直报错,integer无法转换为string。我一直以为是lua脚本写错了,翻文档翻过来又翻过去,写法变了又变,还是解
        。。是字符串连接符,字典用=号连接,  注意fordoend都是连一起,  注意ifthen,  如果local在函数里,是可以访问,非local出了函数一样能用,  doend代码块也是一样,    注意点号表示,只能key是字符串,  注意括号不是必须
C语言与Lua之间的相互调用详解写一个C调用Lua的Demo编译运行C语言调用Lua编译问题总结正确的编译命令问题1:缺少-lm参数问题2:缺少-ldl参数​1、为什么会出现undefinedreferenceto‘xxxxx’错误?​2、-l参数和-L参数写一个C调用Lua的Demo编译运行add.c内容//你需要
1、动态输出打开E:\study\openresty\openresty-1.19.9.1-win64目录下的confginx.conf文件在server中增加一下代码 location/hello{ default_typetext/html; content_by_lua'ngx.say("<p>hello,world</p>")'; }运行后,效果如下图localhost
参见:lipp/lua-websockets:WebsocketsforLua.(github.com)github网址可能需手动转换lipp.github.com/lua-websockets/>github.com/lipp/lua-websocketswebsockets为底层的类似于TCP、UDP的socket(实现上基于更底层的socket),不同于上层的webserver服务端(Service)需并行地支持多
lua发送消息到rabbitmq,我们选择类库lua-resty-rabbitmqstomp 来完成这个任务。类库安装:进入nginx.conf中 lua_package_path 中对应的目录下的resty目录(没有则创建),执行:wget-chttps:/aw.githubusercontent.com/wingify/lua-resty-rabbitmqstomp/master/libes
1Lua介绍Lua是一门以其性能著称的脚本语言,被广泛应用在很多方面。Lua一般用于嵌入式应用,现在越来越多应用于游戏当中,魔兽世界,愤怒的小鸟都有用到。优势Lua极易嵌入到其他程序,可当做一种配置语言。提升应用性能,比如:游戏脚本,nginx,wireshark的脚本兼容性强,可以直接使用C