« 层次结构和状态继承 | 返回首页 | skynet 并发模型的一点改进思路 »

内存块对象的 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 的方法,但你必须自己保证数据的生命期。这里有两种方案:

  1. 保证引用的数据至少能活过 2 个 frame 。
  2. 提供一个用来释放内存的 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 种方法创建出内存块。

  1. 用一个描述数据布局的字符串和一个 table 数组来创建。布局字符串可以描述每个数据段的数据类型(浮点/不同字长的整数),table 数组则是每个字段的数值。
  2. 用一个字符串,以及可选的起始位置及长度来创建一个不可写的内存块。
  3. 用 lightuserdata (指针) 以及可选的长度、数据关联对象来创建。这里的数据关联对象指,让框架帮你引用住这个对象,防止指针引用的内存失效。
  4. 直接指定一个 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

每次使用时引用的方式创建一个临时的bgfx_memory_t对象,
增加关联的memory引用计数,用完马上减少引用计数。

使用过程中,即在(ref > 0)的前提下,memory对象可能会在垃圾回收阶段被回收,
为了保证memory对象存活,创建新的userdata继续引用memory,memory对象复活不会被垃圾回收释放内存。(下一次gc时继续这样如此反复)

风哥,这一刻,你在干嘛~
学习真的不是一件容易的事情,最近看书看得心态都有点崩了。。

Let's Encrypt 的证书有效期只有3个月, 还有1个月不急

我一个月前才更新的证书。

云风,你网站的证书还有1个月过期。哈哈。

好复杂

Post a comment

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