希望 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 下的一个子节点也就轻量的多了。
Comments
@louis
一把辛酸泪啊,昨天就被这个问题卡了一天
Posted by: Jonine | (8) January 8, 2019 02:08 PM
估计Lua三巨头会拒绝,说lua是用来做嵌入小脚本的,而不是大型程序。
Posted by: blues1021 | (7) June 11, 2018 10:53 PM
请教云风,如果需要绑定到LUA中的C++类型中成员有复合结构,即类型中有其他C++类型成员,此类类型的绑定实现是有有好的思路?觉得使用模板来实现泛型的类型绑定还是有些难度!
Posted by: louis | (6) November 17, 2015 05:19 PM
记得前一阵子还被所有lightuserdata共用一个metatable的特性坑过...元表用得少,忽略了这一点。
Posted by: cooldesert | (5) August 24, 2015 10:33 AM
这个问题这几天在 lua 邮件列表被讨论的很充分了。
我最喜欢 Roberto 老大提的方案:
让每个 lightuserdata 的值(甚至所有的值)都可以拥有独立的 metatable 。
Posted by: Cloud | (4) August 21, 2015 07:59 PM
如果我理解得没错,您的意思是:
1. 需要在Lua中存储一些userdata,其内容是C的struct。
2. 需要在Lua中引用这些userdata的部分,比如
f1 = foo.foo1
而且,f1对于foo.foo1是引用语意,即:如果foo中的foo1内容发生了变化,f1可以读到;修改f1会导致foo.foo1的内容发生改变。
3. 这个引用本身需要大量拷贝,所以希望f1本身比较轻量级,尤其是不要因为拷贝了f1而创建大量的对象。
4. 希望f1可以用于table key。两个这样的东西相等当且仅当它们引用同一个userdata的同一个部分。
5. 希望这个引用可以保持这个userdata活着,也就是不希望在f1活着的时候foo已经被回收了。
--
不过,我觉得还有几点可能会影响设计决策,包括:
a. 哪个lua版本?
b. 这些userdata数量多不多?每个userdata大不大?
c. 比如f1 = foo.foo1,那么
c1. 拿到f1,是否能从f1得到foo?比如get_origin(f1) == foo?
c2. 是否只要拿到f1就可以对foo.foo1进行读写操作,而不需要同时再有foo变量?即,可否直接进行get(f1), set(f1, v),而不需要get(foo, f1)或者set(foo, f1, v)
d. 是否要频繁比较这些slice是否相等?
e. 是否要频繁对这些slice进行读写操作?
f. slice被频繁创建吗?被频繁拷贝吗?是否介意 *偶尔* 有几个不同的堆对象表示同一个slice,而它们逻辑上判断相等?
Posted by: wks | (3) August 21, 2015 08:45 AM
发现了什么也不重要,怎么做才重要。
Posted by: 陈冠希 | (2) August 20, 2015 10:25 AM
编程久了会发现,智商不重要情商才重要.
Posted by: Anonymous | (1) August 19, 2015 05:25 PM