不适合采用 System 的一种情况

但我们在实现时却发现了很大的问题。
骨骼相关运算是需要在一个临时短生命期的 Buffer 上进行的。骨骼本身的持久化数据结构占内存不大,但是如果需要做中间运算,就需要把紧凑的数据展开到一个中间 Buffer 中,有关混合、IK 这些运算都是基于这个中间 Buffer 的,而当这些运算完毕,到了蒙皮阶段,中间 Buffer 又没有存在的必要了。
如果遵循 ECS 的原则,我们把这些步骤分离,每个步骤都遍历所有的对象各做一次,就会生成大量的中间 Buffer ,徒耗内存。其实本质上,一个线程只需要一个中间 Buffer 就够了。因为对一个对象做完处理,这个 Buffer 就不再使用,可以被下一组运算复用。
所以,我们近期做的调整就是把这一系列关于骨骼的算法全部集中在一个 System 中处理。
下面再谈谈 Lua 封装代码的处理。
如果整个过程是在 C/C++ 层面完成,那么,这个中间 Buffer 完全可以开在 stack 上。等一系列操作完成后,就会自动回收。但是将 C 函数封装为 Lua 函数后, C stackframe 无法保留任何私有数据。如果你把中间 Buffer 封装成一个 userdata 扔回 Lua ,就面临被滥用的可能。一旦滥用,对 gc 带来额外负担。
我们的做法是把它封装成 userdata 后,隐藏在 C api 的 upvalue 里面,应用层无法直接访问。这一系列针对骨骼数据的 API 就好像 opengl 的 api 那样,有个内在的状态维持在 vm 中。每组调用都从一个显式的调用开始:对于骨骼运算,就是将骨骼数据展开到临时 Buffer 中。
相比昂贵的骨骼运算操作步骤,一次 Lua 到 C 的调用开销算不得什么,所以我们没有将它封装成 [数学库] (https://blog.codingnow.com/2019/03/improved_math3d.html) 的样子(允许单次数学库调用进行足够多的数学运算),还是保留成一个个独立的 Lua API 调用。
我认为以后会有很多类似的情况不适合分 System 分步处理,值得注意。
总结一下,当针对单个对象进行的一系列操作,需要的中间临时资源相对昂贵时,不应该拆分成多步 System 。昂贵的资源可能是内存,也可能是显卡资源。