« lua 模块管理的一点改进 | 返回首页 | 最近玩的几款游戏 »

向量库的一点改进

前段为 3d engine 写的向量运算库小伙伴在用,提了很多意见,所以这段时间一直在改进。

一开始觉得逆波兰表示法的运算表达式不太习惯,觉得需要绕个弯想问题,希望做一个表达式编译的东西,但是用了几天后,又觉得其实不是什么大问题,习惯了就好了。

但心智负担比较大的地方是那个 id 的正负号约定,也就是生命期管理。我想了一下,人为的去管理生命期,有些对象是要长期持有的,有些对象只在当前渲染帧使用,在使用的时候严格区分它们不太现实。

一开始的版本,我需要使用者在计算表达式中用一个 mark 'M' 指令,把一个临时对象转换成一个持久对象,这极大的增加了使用者的负担。尤其是更新一个对象的时候,需要先解除老对象的持久状态,再 mark 新生成的对象。使用的时候需要一直考虑这个对象是不是要更新,用起来太困难了。虽然有强检查,不会把程序弄混乱,但是稍不注意就会报告运行时错(对象 id 失效)。

今天,我做了极大的调整,去掉了之前 mark 语义,增加了引用语义。

之前没有实现引用语义是因为觉得值语义就够用了。现在看起来是不对的。不过引用语义比较难实现,一开始也没有找到合适的实现方式。考虑了很久后,我发现了一条实现引用语义的途径,那就是把引用语义的对象实现为 lua 中的 userdata 。

换句话说,假设 lua 中的 foobar 变量引用了一个 vector ,我们就显示的定义 foobar = math3d.ref "vector" 。

这里 math3d.ref "vector" 会生成一个引用 vector 的对象,在之后的程序中,我们可以改变 foobar 引用的值,但是不再修改 foobar 在 lua 中的值。

之后,我们在原来的指令操作栈中增加一个赋值指令 = ,如果我们想让 foobar 引用一个具体的 vector { 1,0,0,1 } ,就应该写:

command(foobar, { 1, 0, 0, 1} , "=")

这条命令的语义是,将 foobar 这个引用对象和 { 1,0,0,1 } 这个 vector 常量压栈,然后把栈顶的元素赋值给栈次顶的引用对象,然后将栈顶两个东西弹出。

我们之前在 command 序列中,数字 id 表示把一个对象压栈,字符串表示计算指令串,table 表示外部常量;现在需要增加特定的 userdata 表示引用语义的对象。

当然,我还给 foobar 增加了元表,可以用一系列便捷操作。例如 foobar(obj) 相当于把 obj 赋给 foobar , ~foobar 表示取得 foobar 引用对象的 C 指针(lightuserdata)用来传递给底层,等等。


我觉得这一版的设计要人性的多,但是实现上也遇到一些困难。

这是因为,原版的设计中,数据栈上都是数字 id ,所以可以很容易先实现一个 C 层的 stack 结构来操控。这次,数据栈上需要压入 lua userdata 了,就不再能简单的和 C 层数据结构对接。如果粗暴的用一个 hash table 来映射 lua 中的对象,其一,性能受到了损失;其二,userdata 的生命期管理变得非常复杂(需要很小心的加接引用)。

为此,我给 api 增加一个限制:引用语义的对象只可以在当前 command 指令串中使用,不可以把引用语义的对象压入堆栈,然后在下次 command 调用再赋值。所有压入指令栈的引用语义的对象在当前指令结束后,都会退化成值语义的量(如果指令结束还没有弹出)。

在实现层面,我没有修改之前的数据结构,而增加了一个专门保存引用对象的栈,让指令以双栈形式工作。引用对象栈记录的是当前 lua 栈上的数字 index ,等函数调用结束就自动失效了。实现也相对简单。

Comments

是lua5.3的还是支持luajit呀?

Post a comment

非这个主题相关的留言请到:留言本