转载

希望 Lua 可以增加一个新特性 userdata slice

Lua 是一门嵌入式语言,和 host 的联动非常重要。Lua 使用 userdata 来保存 host 里的数据,userdata 非常强大,可以有 metatable 还可以关联一个 uservalue ,可以封装一切 C/C++ 对象,非常强大。但有的时候却稍显不足,似乎缺了点什么,导致一些简单的需求要用很繁琐的方式解决。

有个想法想过很久,今天动了念头用英文写了一遍投递到 lua 邮件列表里去了。

那就是,如果我们可以给 userdata 的值关联一个整数,而不是把 uservalue 关联到 userdata 的对象里那样,可以简化很多事情。

看这样一个例子:

如果我们有一个数据结构:

struct foo { struct foo1 foo1; struct foo2 *foo2; };

如何保存到 lua 里被 lua 引用呢?通常我们使用一个 userdata 封装它:

struct foo * f = lua_newuserdata(L, sizeof(*f));

但是,如果我们想进一步引用这个结构中的一个子结构怎么办?即,如果我们在 lua 里有了 userdata foo, 希望可以通过 foo.foo1 引用其子结构 foo1, 用 foo.foo2 引用 foo2 怎么办?

当然,我们先要给 foo 配置一个 metatable 并实现 index 元方法。这样在语法上就可以用 foo.foo1 和 foo.foo2 了。

但是 foo.foo1 和 foo.foo2 导致是什么?在目前的 lua 中,必须也返回一个 userdata 。那么我们就得在 foo.foo1 时,生成一个新的 userdata 作为 proxy 来访问 foo 对象的一部分。而且一旦访问过 foo.foo1 还得 cache 住这个新生成的 userdata ,保证下次再引用时还能返回同一个 userdata 。

为了保证 foo 和 foo.foo1 的生命期绑定,也就是在 foo.foo1 还在用的时候 foo 不被回收掉,往往我们还需要在 foo.foo1 的 uservalue 中保存一份 foo 的引用。这样才可以让 gc 能正确工作。

如果我们可以给 userdata 的值(而不是 object)关联一个整数就不一样了。例如,我们用 0 表示 foo 本身,1 表示 foo.foo1 ,2 表示 foo.foo2 。当 foo 被创建出来的时候,关联值默认是 0 ,也就是指代 foo 本身。

当我们运行到 foo.foo1 时,在 index 元方法中可以返回同一个 foo userdata ,但关联上新值 1 。注意,这个 1 是跟着返回值走的,并不在 userdata object 上,所以并不影响之前的 foo 对象。

这样,同一个 foo userdata 就有了对 C 对象的多种表达,在 lua 中也可以正常的做比较,做 table 的键。

为了实现这个新特性,并不需要修改 lua 原有的定义,也不需要增加新类型。只需要把以前类型为 LUA_TUSERDATA 的值在实现上变成两个整数组。第一个整数是一个 userdata 的 handle ,而所有真正的 userdata object 指针都保存在 lua vm 的 global state 里即可。第二个整数是关联其上的 slice 值,表示这个值应用 userdata object 的哪一个切片。

这一对值可以随 lua 的赋值质量做值拷贝,而不像之前的 userdata 只是做引用拷贝,这样就可以作到对同一个 C 对象的不同表达。

我们只需要增加两个新的 C API 就够了:

int lua_getuserslice(lua_State *L, int index); void lua_setuserslice(lua_State *L, int index, int slice);

lua_getuservalue 的用法也基本一致。

有了这个特性,封装 GUI 对象或 3d engine 中的对象要容易的多。

当我们在 lua 里写 OBJECT 和 OBJECT.X 或 OBJECT.X.Y 时,它们可以都指向同一个 userdata object ,只是 slice 不同。这样 OBJECT.X 获得 OBJECT 下的一个子节点也就轻量的多了。

正文到此结束
Loading...