|
相关文章:
本章介绍slua与luaGC,UEGC的关系,是比较重要的一点。
UE4使用GC管理UObject,lua也有GC管理l对象,因此UE和lua进行数据传递时,slua需要准确的加引用和减引用操作。这样,UE在GC时知道哪些UObject正在被lua runtime使用,相应的,lua在gc时也能知道哪些对象正在被UE4 runtime使用,从而进行正确的GC行为。但是也要记住,UE可以显式调用Destroy()销毁Actor,也可以使用MarkPendingKill()来销毁UObject,即使还有引用存在。
UObject从UE传递到lua
我们知道,UObject会被UE的GC管理,因此首先会想到把UObject传递到lua该如何处理引用?
严格的说,UObject本身并没有传到lua,前文说过,真正传到lua的是UserData结构体。
入口代码如下:- int LuaObject::push(lua_State* L, UObject* obj, bool rawpush, bool ref) {
- if (!obj) return pushNil(L);
- if (!rawpush) {
- if (auto it = Cast<ILuaTableObjectInterface>(obj)) {
- return ILuaTableObjectInterface::push(L, it);
- }
- }
- if (auto e = Cast<UEnum>(obj)
- return pushEnum(L, e);
- else if (auto c = Cast<UClass>(obj))
- return pushClass(L, c);
- else if (auto s = Cast<UScriptStruct>(obj))
- return pushStruct(L, s);
- else
- return pushGCObject<UObject*>(L,obj,&#34;UObject&#34;,setupInstanceMT,gcObject,ref);
- }
复制代码 我们先看rawpush的情况,rawpush会直接调用pushGCObject函数,函数内容如下:- template<typename T>
- static int pushGCObject(lua_State* L,T obj,const char* tn,lua_CFunction setupmt,lua_CFunction gc,bool ref) {
- if(getFromCache(L,obj,tn)) return 1;
- lua_pushcclosure(L,gc,0);
- int f = lua_gettop(L);
- int r = pushType<T>(L,obj,tn,setupmt,f);
- lua_remove(L,f); // remove wraped gc function
- if (r) {
- addRef(L, obj, lua_touserdata(L, -1), ref);
- cacheObj(L, obj);
- }
- return r;
- }
复制代码 把UObject传递到lua,核心步骤为在lua中创建一个UserData,并把ud属性与该UObject进行关联,该操作在pushType函数中完成。 对一个UObject创建UserData后,会使用cacheObj缓存起来,这样当下一次把该UObject传递到lua时,就可以通过getFromCache直接获取对应UserData。缓存结构是lua中的一张表,key为UObject指针,value为UserData指针。
创建UserData只完成了一半工作,剩下工作就是处理UE的GC,由objRefs完成。
objRefs
在LuaState类中有objRefs属性,其类型为TMap<UObject*, GenericUserData*>,记录了UObject和在lua中创建的UserData键值对。pushGCObject中的addRef函数,就是在objRefs中添加一条记录,那objRefs又是如何影响UE的GC呢?这和LuaState有关。
LuaState继承自FGCObject,因此UE在GC时会调用到它的AddReferencedObjects方法,代码如下:- void LuaState::AddReferencedObjects(FReferenceCollector & Collector)
- {
- for (UObjectRefMap::TIterator it(objRefs); it; ++it)
- {
- UObject* item = it.Key();
- GenericUserData* userData = it.Value();
- if (userData && !(userData->flag & UD_REFERENCE))
- {
- continue;
- }
- Collector.AddReferencedObject(item);
- }
- // do more gc step in collecting thread
- // lua_gc can be call async in bg thread in some isolate position
- // but this position equivalent to main thread
- // we just try and find some proper async position
- if (enableMultiThreadGC && L) lua_gc(L, LUA_GCCOLLECT, 128);
- }
复制代码 方法内部会遍历objRefs,并对符合条件的UserData对应的UObject添加引用,这些UObject正在被lua使用。因此,这些UObject在UE中至少有1个引用,从而不会由于没有引用而被删除。
有了添加引用,自然也有删除引用,删除引用代码在unlinkUObject函数,内部首先会把objRefs中的UObject记录移除,然后把UserData的flag设置上UD_HADFREE属性,表示该UserData已经被释放,之后再把lua缓存表中对应记录也删除。
删除引用操作会在几个时机执行:
这个显而易见,当UObject对应的对象在lua中无法被引用到,下次gc时就会被删除。我们在pushGCObject时在元表上设置了__gc方法,对应的C++方法为gcObject,其主要操作就是调用unlinkUObject,删除objRefs中对应的记录。- int LuaObject::gcObject(lua_State* L) {
- CheckUDGC(UObject,L,1);
- removeRef(L,UD);
- return 0;
- }
复制代码- void LuaObject::removeRef(lua_State* L,UObject* obj) {
- auto sl = LuaState::get(L);
- sl->unlinkUObject(obj);
- }
复制代码 2. UObject被显式删除
UE中,我们可以对actor调用destroy方法显式删除它,也可以把UObject标记为PendingKill进行显式删除,即使当前还有对它们的引用,因此objRefs中的记录的UObject可能在任何时候被UE删除掉。为了应对这一情况,LuaState专门继承了FUObjectDeleteListener,当有UObject被销毁时,LuaState会通过NotifyUObjectDeleted函数收到通知,然后把objRefs中对应的记录删掉。因为unlinkUObject中会对UserData的flag设置UD_HADFREE属性,所以当lua再访问该对象时就知道它已经被删除了,不会再去访问它的地址。- void LuaState::NotifyUObjectDeleted(const UObjectBase * Object, int32 Index)
- {
- PROFILER_WATCHER(w1);
- unlinkUObject((const UObject*)Object);
- }
复制代码
综上,GC示意图如下:
UStruct从UE传递到lua
UStruct本身不会被GC管理,但UStruct会对内部的ObjectProperty产生引用,因此把UStruct传递到lua时,也需要处理GC。
Push Struct代码如下:- int pushUStructProperty(lua_State* L,UProperty* prop,uint8* parms,bool ref) {
- auto p = Cast<UStructProperty>(prop);
- ensure(p);
- auto uss = p->Struct;
- if (LuaWrapper::pushValue(L, p, uss, parms))
- return 1;
- uint32 size = uss->GetStructureSize() ? uss->GetStructureSize() : 1;
- uint8* buf = (uint8*)FMemory::Malloc(size);
- uss->InitializeStruct(buf);
- uss->CopyScriptStruct(buf, parms);
- return LuaObject::push(L, new LuaStruct(buf,size,uss));
- }
复制代码 可以看到,slua用LuaStruct把ustruct给包装起来,然后把LuaStruct传递到了lua,当然,最终传到Lua的还是UserData。
LuaStruct- struct SLUA_UNREAL_API LuaStruct : public FGCObject {
- uint8* buf;
- uint32 size;
- UScriptStruct* uss;
- LuaStruct(uint8* buf,uint32 size,UScriptStruct* uss);
- ~LuaStruct();
- virtual void AddReferencedObjects(FReferenceCollector& Collector) override;
- #if (ENGINE_MINOR_VERSION>=20) && (ENGINE_MAJOR_VERSION>=4)
- virtual FString GetReferencerName() const override
- {
- return &#34;LuaStruct&#34;;
- }
- #endif
- };
复制代码 LuaStruct最大的特定就是也继承FGCObject,并且在AddReferencedObjects函数中对Struct的各个属性添加引用。
先看一下UE是怎么处理UStruct对UObject引用的,它会先扫描UStruct内部各个成员,把成员信息编码为ReferenceToken,GC时不用在管Struct结构,可以根据Token信息直接对UObject添加引用,效率较高。
关于UE的GC,可见之前分析的文章:
slua没有使用UE生成的ReferenceToken信息,而是根据反射自己实现了UStruct引用收集逻辑,主要代码在LuaReference中。由于基于反射,需要运行时再遍历UStruct成员,包括那些不参与GC的成员,因此效率自然比UE的ReferenceToken机制低。
综合来看,UStruct传递到lua后的GC处理和UObject类似,都通过传递FGCobject实现,只是UStruct引用分析过程由slua自己实现。
变量从lua传到UE
同样的,对象从lua传到UE后,相当于UE也对它有引用,lua在gc时应该要统计到这个引用。
slua实现里,lua中变量传递到UE时,会创建一个LuaVar类型变量,通过LuaVar对lua变量产生引用。就以table传递为例子。
当一个table在stack中时,我们可以把table的index传递给LuaVar的构造函数,LuaVar就知道要与哪个变量关联。- LuaVar::LuaVar(lua_State* l,int p):LuaVar() {
- set(l,p);
- }
复制代码 set函数中,会根据lua_type设置LuaVar的type,当type为LUA_TABLE时,会执行如下处理:- case LV_FUNCTION:
- case LV_TABLE:
- case LV_USERDATA:
- alloc(1);
- lua_pushvalue(l,p);
- vars[0].ref = new RefRef(l);
- vars[0].luatype=type;
- break;
复制代码- struct RefRef: public Ref {
- RefRef(lua_State* l);
- virtual ~RefRef();
- bool isValid() {
- return ref != LUA_NOREF;
- }
- void push(lua_State* l) {
- lua_geti(l,LUA_REGISTRYINDEX,ref);
- }
- int ref;
- int stateIndex;
- };
复制代码 首先,alloc(1)会新建一个lua_var类型的变量,关键看RefRef属性,会被设置为一个新建的RefRef对象。RefRef对象创建时,会通过一个全局表给lua对象加引用,这样lua就能知道UE正在使用这个对象。在LuaVar析构时同样会调用RefRef对象的析构函数,然后把引用解除。- LuaVar::RefRef::RefRef(lua_State* l)
- :LuaVar::Ref()
- {
- ref=luaL_ref(l,LUA_REGISTRYINDEX);
- stateIndex = LuaState::get(l)->stateIndex();
- }
- LuaVar::RefRef::~RefRef() {
- if(LuaState::isValid(stateIndex)) {
- auto state = LuaState::get(stateIndex);
- luaL_unref(state->getLuaState(),LUA_REGISTRYINDEX,ref);
- }
- }
复制代码 |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|