内存块对象的 Lua 封装
最近给 bgfx 的 lua binding 做了一点改进,因为修改了原有的 api 语义,所以需要做一点记录。
对于 3d 库来说,API 涉及大量的内存块的操作。创建 Buffer ,贴图,shader ,都需要输入一个数据块。大多数数据块是只读的,少部分是需要回写的。对于只读数据块,封装层可以用 lua string 替代,可写的用 userdata 。
bgfx 自己抽象了一个叫做 Memory 的结构,用来统一描述这类内存块对象。按 bgfx 的定义,Memory 的构造由用户决定,而释放通常由 bgfx 管理,而非调用者。
即,用户负责构造出 Memory 对象,将数据拷贝进去,然后再传递给 bgfx 的 api 后就可以撒手不管了。但是,如果你构造出 Memory 对象不传递给 bgfx 则会造成内存泄漏(因为没有任何直接释放它的方法);也不可以将一个 Memory 对象使用多次(传递给 bgfx 多次),因为一旦传给 bgfx ,就失去了对象的控制权。
这个用法,在 C/C++ 层面是非常好用的,但对于 lua binding 来说,很让人苦恼。因为很难安全的封装。
如果你将 Memory 封装成一个 Lua 的 userdata ,当用户构造出来后却因为种种原因没有使用(可能是发生了 error ,阻断了正常的执行流程),你没有办法消化掉它(因为没有直接释放的 api )。如果用户构造出一个对象,却无法多次使用,也会造成使用上的困扰。
Memory 的构造会多一次内存拷贝,可能也是个浪费。bgfx 提供了用引用来创建 Memory 的方法,但你必须自己保证数据的生命期。这里有两种方案:
- 保证引用的数据至少能活过 2 个 frame 。
- 提供一个用来释放内存的 callback 。
如果我们用方案 1 ,就需要自己将所有传入 bgfx 的 lua 内存对象暂时引用起来,每隔 2 frame 解除引用;如果用方案 2 , 需要考虑 bgfx 是一个多线程库,而跨线程操作 lua 的 vm 对象需要考虑线程安全问题。
基于这些难点,我一开始做 bgfx 的 binding 时,没有抽象出一个 memory 对象,而是在所有相关 api 处都直接处理输入参数。根据参数是 table / string / userdata 来临时创建出 Memory 对象,传给 bgfx 了事。不把细节暴露出去。
但随着日益开发,我们需要越来越多样的数据构造方法,这使得维护一组 api 变得负担颇大。有些复杂的构造方法(例如传入指针加偏移量等多个信息)参数过多,让相关 api 的参数的复杂性也变得不可接受。所以我还是决定抽象出 lua 层面的 memory 对象,一劳永逸的解决这个问题。
先说结果:
我给 binding 库增加了 bgfx.memory_buffer
这个新 api ,可以用 4 种方法创建出内存块。
- 用一个描述数据布局的字符串和一个 table 数组来创建。布局字符串可以描述每个数据段的数据类型(浮点/不同字长的整数),table 数组则是每个字段的数值。
- 用一个字符串,以及可选的起始位置及长度来创建一个不可写的内存块。
- 用 lightuserdata (指针) 以及可选的长度、数据关联对象来创建。这里的数据关联对象指,让框架帮你引用住这个对象,防止指针引用的内存失效。
- 直接指定一个 size ,创建一个可写的内存块。
大部分过去的 api 依旧兼容,但少部分 api ,例如 bgfx.create_vertex_buffer
就必须传入这样一个内存块对象,而不能像过去那样传入 table 。具体使用上的变化可以参考 example 。
我是如何实现这个东西的呢?
首先,这个 lua 内存对象并非对 bgfx::Memory 的直接封装。它在构造出来后,并非 bgfx 的 Memory 对象。所以即使你构造出来不用,也可以被安全的回收。
一旦它被 bgfx 的 api 调用,那么就会用数据引用的形式临时创建出一个 Memory 对象,传递给 bgfx 。并且递增了一个内存引用。当 bgfx 不再使用它后(在两个 frame 之内就会释放)回调函数会递减这个引用。
这个对象的 __gc
方法会检查引用计数。只有是 0 的时候才会安全的释放。如果引用计数不为 0 ,那么会让这个对象多活一小段时间。
怎样做到让一个进入 __gc
方法的对象多活一段时间?我使用的方法是在 __gc
方法内临时创建出一个 userdata ,并把自身挂载在其 uservalue 上。这个临时的 userdata 的 __gc
会再次检查引用计数,如果下次还是未能到 0 ,就继续这个过程。
Comments
Posted by: Larry Xu | (6) July 7, 2020 11:47 AM
Posted by: 蜗牛 | (5) June 12, 2020 05:48 PM
Posted by: dwing | (4) June 12, 2020 09:56 AM
Posted by: Cloud | (3) June 11, 2020 03:27 PM
Posted by: 谍海浮沉 | (2) June 11, 2020 12:56 PM
Posted by: yongxinchang | (1) June 11, 2020 09:20 AM