可以在哪个网站做封面赚钱,新媒体一键发布平台,wordpress问答社区主题,网站建设有哪些企业LuaCallCS
1. 传递C#对象到Lua
XLua在C#维护了两个数据结构#xff0c;ObjectPool和ReverseMap。
首次传递一个C#对象obj到Lua时#xff0c;对象被加入到ObjectPool中#xff0c;并为它创建一个唯一标识objId#xff0c;建立obj和objId的双向映射。
ObjectPool: objId-… LuaCallCS
1. 传递C#对象到Lua
XLua在C#维护了两个数据结构ObjectPool和ReverseMap。
首次传递一个C#对象obj到Lua时对象被加入到ObjectPool中并为它创建一个唯一标识objId建立obj和objId的双向映射。
ObjectPool: objId-obj ReverseMap: obj-objId 如果该对象的类型是第一次传到Lua还会为类型创建一个元表typeMetatable。
typeMetatable包含类成员的访问方法。
把typeMetatable注册到Lua的全局表中,这样就不会被lua gc掉。
LUA_REGISTRY: typeFullName-typeMetatable
创建一个userdata对象放到C#和Lua交互的栈上userdata的值设为objIduserdata的元表设为typeMetatable。这样userdata就可以通过value找到C#对应的对象通过元表找到类成员的访问方法。
userdata: value objId, metatable typeMetatable
至此完成了C#对象到Lua的传递lua从栈上拿到这个userdata对象。
对于引用类型还会把这个userdata记录到Lua的全局表cache_ref。
cache_ref:objId-userdata
下次再传递同一个对象时通过obj在ReverseMap里找到objId通过objId去cache_ref里找到userdata放到lua栈上。
cache_ref表的value是弱引用userdata没有其他引用后gc时会把cache_ref里的键值对删除。
2. 对象释放
对象的释放有两种方式。
Lua gc
在userdata的元表里设置了__gc方法 typeMetatable{ __gc LuaGC, }
当一个userdata被gc时触发LuaGC方法。从userdata上拿到objId, 从ObjectPool和ReverseMap中移除对应的C#对象。cache_ref里的userdata是弱引用会被gc清理掉。
Destroy UnityEngine.Object
XLua可以定期检查ObjectPool里的UnityEngine.Object对象如果已经被Destroy则主动从ObjectPool和ReverseMap中删除C#对象可以被GC。userdata对象等待lua gc再释放。
3. Lua访问C#成员
一个C#对象在Lua中有一个对应的userdata对象。比如一个GameObject对象go访问它的transform成员。 local transform go.transform
go的元表是GameObject的typeMetatableXLua为GameObject生成了一个_g_get_transform C#函数并注册到typeMetatable中
这部分比较复杂可以简单的认为类的元表中生成了类成员的访问方法通过成员变量的名字可以查询到对应的方法。GameObject_metatable{__index function(key)field {transform _g_get_transform,...},...if fields[key] ~ nil then return fields[key]end ...end,__newIndex同理。
}
访问字段transform会触发__index方法查询到gget_transform这是xlua生成的一段C#函数该函数通过userdata拿到objId从而在ObjectPool中拿到C#对象go将go.transform传给lua。
4. Struct对象的传递
默认情况下将一个Struct对象传递给Lua首先会装箱成一个Object对象记录到ObjectPool中生成objId再创建一个userdata给luauserdata中记录着objId。跟引用类型不同值类型的userdata不会存到cache_ref中。 所以lua每读取一个struct都会创建一个C#对象和一个userdata。XLua提供了一个优化方案GCOptimize。
5. GCOptimize
可以对指定的Struct类型加上GCOptimize标签XLua会为其生成专门的Push,Get,Update代码。
以Vector3为例。
Push
local pos transform.position
C#传递一个Vector3到Lua时创建一个userdata并设置它的元表。
userdata申请的value申请12个字节(Vector3有3个float)把Vector3对象的xyz的值依次复制到userdata的三个float上。userdata不再记录objId而是直接存储struct的值。
通过GCOptimize的方式省掉了C#的gc消耗不过每次push一个vector3对象还会创建一个userdata。
Get
transform.positon pos
XLua支持两种从lua传值对象到C#。
第一种是传userdata即这个对象本来就是从C#传到lua的lua再传回去。这时会直接把userdata里的值取出来赋给C#的Vector3对象。没有gc性能比较好。
第二种是构建一个字段相同的table传给C#transform.positon {x0,y0,z0}。xlua会查询table里同名的字段复制值给c#的成员变量。由于根据变量名字的字符串去查表这个性能没有第一种好。
Update
pos.x 1
lua修改一个GCOptimize的userdata。
先Get上文讲了会创建一个Vector3对象复制userdata的值。然后在C#修改它的值x。
接下来Update操作拿到栈顶的userdata把Vector3对象的值复制到userdata。
6. 在Lua中读写transform position
修改transform的position是一个很常见的需求看看下面这段lua代码正确吗
transform.position.x 1
这段代码可以执行但并没有效果transform的position不会被改变。
如果这段代码用c#写会直接编译器报错。来看看Transform里的position源码
public Vector3 position
{get {Vector3 ret;this.get_position_Injected(out ret);return ret;}set this.set_position_Injected(ref value);
}
position并不是一个变量而是一个属性。所以在C#中transform.position是一个返回值C#不允许修改一个返回值因为修改一个临时变量并没有意义。所以在C#中你得这么写
var pos transform.position;
pos.x 1;
transform.position pos;
再回头看看那句lua代码 transform.position.x 1 它不过是把get出来的值修改了要想把位置修改只能通过position的set访问器。
local pos transform.position;
pos.x 1;
transform.position pos; 看起来很繁琐但这不是lua的问题在c#里就这么繁琐。
再来分下这段代码的性能对Vector3开启GCOptimize,
local pos transform.position; --transform Get position: Push Vector3有一个userdata gc
pos.x 1; --Vector3 set x: 1.Get Vector3复制userdata的值到Vector32.修改Vector3的x3.Update Vector3,把Vector3的值复制回userdata
transform.position pos; --transform Set position: Get Vector3,复制userdata的值给Vector3这个性能不差。
接下来介绍一种性能比较好的方式。
7. 不带来GC的值类型传递方式
可以看到引入GCOptimize之后除了Push操作有lua GCGet和Update是没有GC的。
传递一个userdata到C#比传递table效率更高因为传递table会通过字符串查表。
修改一个table的值比修改一个userdata的值效率更高(看起来但没测过)因为每修改userdata的一个成员就会拷贝两次Vector3而修改一个table只用一次查表。
常见的一个需求是在lua维护一个位置信息lua会更新这个位置数据并设置到c#对象上。如果创建一个Vector3的userdata对象传值给C#比较快但更新它的值比较低效。如果创建一个table对象更新它的值比较高效但传给C#的时候会有3次查表。
综上所述推荐最简单的方案在C#封装一些Util函数把对象的成员变量通过参数的方式进行传递在lua维护x,y,z。
public static void GetPosition(Transform t, out float x, out float y, out float z) {Vector3 v t.position;x v.x;y v.y;z v.z;
}
public static void SetPosition(Transform t, float x, float y, float z) {t.position new Vector3(x, y, z);
}
CSCallLua
1.传递Lua函数到c#
首先在lua全局表注册lua函数避免被GC
LUA_REGISTRY: luaReference-luaFunction
创建DelegateBridgeBase对象记住luaReference
bridge:luaReference
bridge对象里根据类型创建Delegate一个lua函数可能在C#侧被用作不同类型的Delegate
bridge:luaReferenceDelegateType,Delegate
Delegate 的实例是生成的C#代码通过luaReference访问lua函数这里面有对bridge的引用所以持有这个delegate对象就不会释放bridge对象。
最后返回Delegate给使用者持有。bridge是局部变量没有被其他对象引用。
2. DelegateBridge gc过程
c#侧释放对delegate对象delegate对象释放后bridge的引用计数归零被GC在gc方法中从lua全局注册表清理luaReference对应的lua函数引用计数减一。
交互总结
无论是cs call lua还是lua call cs原理是一样的。
从A环境传递一个对象a到B环境会先在A环境的全局存储这个对象并建立一个a_id保证这个对象不被GC把a_id传递到B环境并构建一个包装对象a_wrapper包含a_id这样a_wrapper可以通过a_id访问到a。a_wrapper的GC方法里需要把A全局表里的a清除掉这样就能触发a的GC。
对于引用对象还会记录到一个弱引用表a_id,a_wrapper下次传递a对象时就能找到a_wrapper不用再创建。
globalReg: a_id-a
[a_wrapper{a_id,GC{delete in globalReg}}]
weaktable:aid,a_wrapper 两个GC系统的延迟GC问题
一个C#对象被lua持有着引用当lua释放引用时c#对象不会直接gc需要等lua gclua gc可能会很慢触发但它背后的引用的c#对象可能占用着较大的内存。
解决方案是lua对象释放的时候手动调用一下c#对象的释放把较大的内存释放掉比如RawFileAsset对象中的byte数组datalua释放这个对象时手动把data置null这样data就可以及时被GC不用等lua gc。