Unlua基本原理

源码阅读

Posted by Bob on May 6, 2025
动态绑定

UE可以使用AddUObjectCreateListener侦听创建UObject


//UObjectBase.cpp
void UObjectBase::AddObject(FName InName, EInternalObjectFlags InSetInternalFlags, int32 InInternalIndex, int32 InSerialNumber)
{
	NamePrivate = InName;
	EInternalObjectFlags InternalFlagsToSet = InSetInternalFlags;
	if (!IsInGameThread())
	{
		InternalFlagsToSet |= EInternalObjectFlags::Async;
	}
	if (ObjectFlags & RF_MarkAsRootSet)
	{		
		InternalFlagsToSet |= EInternalObjectFlags::RootSet;
		ObjectFlags &= ~RF_MarkAsRootSet;
	}
	if (ObjectFlags & RF_MarkAsNative)
	{
		InternalFlagsToSet |= EInternalObjectFlags::Native;
		ObjectFlags &= ~RF_MarkAsNative;
	}
	GUObjectArray.AllocateUObjectIndex(this, InternalFlagsToSet, InInternalIndex, InSerialNumber);
	check(InName != NAME_None && InternalIndex >= 0);
	HashObject(this);
	check(IsValidLowLevel());
}

//UObjectArray.cpp
void FUObjectArray::AllocateUObjectIndex(UObjectBase* Object, EInternalObjectFlags InitialFlags, int32 AlreadyAllocatedIndex, int32 SerialNumber)
{
    ...
    for (int32 ListenerIndex = 0; ListenerIndex < UObjectCreateListeners.Num(); ListenerIndex++)
	{
		UObjectCreateListeners[ListenerIndex]->NotifyUObjectCreated(Object,Index);
	}
}

void FUObjectArray::AddUObjectCreateListener(FUObjectCreateListener* Listener)
{
	check(!UObjectCreateListeners.Contains(Listener));
	UObjectCreateListeners.Add(Listener);
}

UnLuaModule.cpp里注册了UObject的创建,还替换了UObject的Input绑定函数


        virtual void SetActive(const bool bActive) override
        {
            if (bIsActive == bActive)
                return;

            if (bActive)
            {
                OnHandleSystemErrorHandle = FCoreDelegates::OnHandleSystemError.AddRaw(this, &FUnLuaModule::OnSystemError);
                OnHandleSystemEnsureHandle = FCoreDelegates::OnHandleSystemEnsure.AddRaw(this, &FUnLuaModule::OnSystemError);
                GUObjectArray.AddUObjectCreateListener(this);
                GUObjectArray.AddUObjectDeleteListener(this);
                ....
            }
        }

        virtual void NotifyUObjectCreated(const UObjectBase* ObjectBase, int32 Index) override
        {
            // UE_LOG(LogTemp, Log, TEXT("NotifyUObjectCreated : %p"), ObjectBase);
            if (!bIsActive)
                return;

            UObject* Object = (UObject*)ObjectBase;

            const auto Env = EnvLocator->Locate(Object);
            // UE_LOG(LogTemp, Log, TEXT("Locate %s for %s"), *Env->GetName(), *ObjectBase->GetFName().ToString());
            Env->TryBind(Object);
            Env->TryReplaceInputs(Object);
        }

经过一系列检查后,最终通过Bind来动态绑定


UnLua::FLuaEnv* ULuaEnvLocator::Locate(const UObject* Object)
{
    if (!Env)
    {
        Env = MakeShared<UnLua::FLuaEnv, ESPMode::ThreadSafe>();
        Env->Start();
    }
    return Env.Get();
}

FString ULuaModuleLocator::Locate(const UObject* Object)
{
    const UObject* CDO;
    if (Object->HasAnyFlags(RF_ClassDefaultObject | RF_ArchetypeObject))
    {
        CDO = Object;
    }
    else
    {
        const auto Class = Cast<UClass>(Object);
        CDO = Class ? Class->GetDefaultObject() : Object->GetClass()->GetDefaultObject();
    }

    if (CDO->HasAnyFlags(RF_NeedInitialization))
    {
        // CDO还没有初始化完成
        return "";
    }

    if (!CDO->GetClass()->ImplementsInterface(UUnLuaInterface::StaticClass()))
    {
        return "";
    }

    return IUnLuaInterface::Execute_GetModuleName(CDO);
}

/**
 * Bind a Lua module for a UObject
 */
bool UUnLuaManager::Bind(UObject *Object, const TCHAR *InModuleName, int32 InitializerTableRef)
{
    check(Object);

    const auto Class = Object->IsA<UClass>() ? static_cast<UClass*>(Object) : Object->GetClass();
    lua_State *L = Env->GetMainState();

    if (!Env->GetClassRegistry()->Register(Class))
        return false;

    // try bind lua if not bind or use a copyed table
    UnLua::FLuaRetValues RetValues = UnLua::Call(L, "require", TCHAR_TO_UTF8(InModuleName));
    FString Error;
    if (!RetValues.IsValid() || RetValues.Num() == 0)
    {
        Error = "invalid return value of require()";
    }
    else if (RetValues[0].GetType() != LUA_TTABLE)
    {
        Error = FString("table needed but got ");
        if(RetValues[0].GetType() == LUA_TSTRING)
            Error += UTF8_TO_TCHAR(RetValues[0].Value<const char*>());
        else
            Error += UTF8_TO_TCHAR(lua_typename(L, RetValues[0].GetType()));
    }
    else
    {
        BindClass(Class, InModuleName, Error);
    }

    if (!Error.IsEmpty())
    {
        UE_LOG(LogUnLua, Warning, TEXT("Failed to attach %s module for object %s,%p!\n%s"), InModuleName, *Object->GetName(), Object, *Error);
        return false;
    }

    // create a Lua instance for this UObject
    Env->GetObjectRegistry()->Bind(Class);
    Env->GetObjectRegistry()->Bind(Object);

    // try call user first user function handler
    int32 FunctionRef = PushFunction(L, Object, "Initialize");                  // push hard coded Lua function 'Initialize'
    if (FunctionRef != LUA_NOREF)
    {
        if (InitializerTableRef != LUA_NOREF)
        {
            lua_rawgeti(L, LUA_REGISTRYINDEX, InitializerTableRef);             // push a initializer table if necessary
        }
        else
        {
            lua_pushnil(L);
        }
        bool bResult = ::CallFunction(L, 2, 0);                                 // call 'Initialize'
        if (!bResult)
        {
            UE_LOG(LogUnLua, Warning, TEXT("Failed to call 'Initialize' function!"));
        }
        luaL_unref(L, LUA_REGISTRYINDEX, FunctionRef);
    }

    return true;
}

这三个是预先bind的UBlueprintFunctionLibrary / UAnimNotifyState / UAnimNotify

静态绑定

1.对实现UUnLuaInterface的对象处理 2.通过BEGIN_EXPORT_CLASS等宏标记

官方静态导出说明

GC

当lua侧UObject为LUA_NOREF,将其设置为0xDEAD


  const static UObject* ReleasedPtr = (UObject*)0xDEAD;

  FORCEINLINE bool IsReleasedPtr(const void* Ptr) { return Ptr == ReleasedPtr; }


    bool IsUObjectValid(UObjectBase* ObjPtr)
    {
        if (!ObjPtr || ObjPtr == LowLevel::ReleasedPtr)
            return false;
        return (ObjPtr->GetFlags() & (RF_BeginDestroyed | RF_FinishDestroyed)) == 0 && ObjPtr->IsValidLowLevelFast();
    }


            check(lua_isuserdata(L, -1));
            bool bTwoLvlPtr;
            void* Userdata = GetUserdataFast(L, -1, &bTwoLvlPtr);
            check(bTwoLvlPtr)
            *((void**)Userdata) = (void*)LowLevel::ReleasedPtr;
            lua_settop(L, Top);

UE5.5编译

先自己尝试修改了一波,后来发现这个分支https://github.com/Lanaoti/Tencent-UnLua/commit/06a8caa0a84cce26bcea7a8877b8e132e1dd23c7#diff-a30b19f23c39f0bd3b99f75d26bc3b8fc3b4612fd79d021991061114d85fc8c6

启动编译错误修改如下

  • TPSProject.Target.cs和TPSProjectEditor.Target.cs中DefaultBuildSettings = BuildSettingsVersion.V2修改为DefaultBuildSettings = BuildSettingsVersion.V5;
  • 所有.Build.cs中的bEnableUndefinedIdentifierWarnings = false; 修改为UndefinedIdentifierWarningLevel = WarningLevel.Off;
  • UnLuaDefaultParamCollectorUbtPlugin.ubtplugin.csproj中net6.0改为net8.0
  • UnLuaDefaultParamCollectorUbtPlugin.cs修改Generate函数

修改如下

        //UnLuaDefaultParamCollectorUbtPlugin.cs
        private void Generate()
        {
            /*
            foreach (UhtPackage package in Session.Packages)
            {
                var moduleType = package.Module.ModuleType;
                ParseModule(package.Module.Name, moduleType, package.Module.OutputDirectory);
                if (moduleType != UHTModuleType.EngineRuntime && moduleType != UHTModuleType.GameRuntime)
                {
                    continue;
                }
                QueueClassExports(package, package);
            }
            
            // Wait for all the classes to export
            Finish();
            */
            
            foreach (UhtModule module in Session.Modules)
            {
                foreach (UhtPackage package in module.Packages)
                {
                    var moduleType = package.Module.Module.ModuleType;
                    ParseModule(package.Module.Module.Name, moduleType, package.Module.Module.OutputDirectory);
                    if (moduleType != UHTModuleType.EngineRuntime && moduleType != UHTModuleType.GameRuntime)
                    {
                        continue;
                    }
                    QueueClassExports(package, package);
                }
            }
            // Wait for all the classes to export
            Finish();
        }

  • TChooseClass改为std::conditional
  • EAutomationTestFlags::ApplicationContextMask改为EAutomationTestFlags_ApplicationContextMask

其他修改参见上面分支

启动流程
  • Unreal启动时调用UnLuaModule模块的StartupModule,先做一些配置初始,侦听PIE和Map的开始结束流程
  • 启动PIE时,调用SetActive,主要关注UObject创建时动态绑定Lua和启动LuaEnv
  • 把PreBindClasses(UBlueprintFunctionLibrary/UAnimNotifyState/UAnimNotify)的所有子类也lua绑定
  • 第一个UObject(一般是GameInstance)创建时才启动UUnLuaManager
  • require一个lua文件,调用LoadFromFileSystem读取文件执行lua
绑定一个UObject

下表解释-4的来历 lua_rawset(L, -4); // INSTANCE.Object = RAW_UOBJECT

index value index
6 UserData-Object -1
5 “Object” -2
4 UserData-Object -3
3 LuaTable-INSTANCE -4
2 LightUserData-Object -5
1 UnLua_ObjectMap -6

lua_rawset(L, -4);执行完毕后

index value index
4 UserData-Object -1
3 LuaTable-INSTANCE -2
2 LightUserData-Object -3
1 UnLua_ObjectMap -4

lua_rawgeti(L, LUA_REGISTRYINDEX, ClassBoundRef); 执行完毕后

index value index
5 TypeModule -1
4 UserData-Object -2
3 LuaTable-INSTANCE -3
2 LightUserData-Object -4
1 UnLua_ObjectMap -5

int32 TypeMetatable = lua_getmetatable(L, -2); 执行完毕后

index value index
6 METATABLE_UOBJECT -1
5 REQUIRED_MODULE -2
4 UserData-Object -3
3 LuaTable-INSTANCE -4
2 LightUserData-Object -5
1 UnLua_ObjectMap -6

lua_pushstring(L, “Overridden”);

index value index
7 “Overridden” -1
6 METATABLE_UOBJECT -2
5 REQUIRED_MODULE -3
4 UserData-Object -4
3 LuaTable-INSTANCE -5
2 LightUserData-Object -6
1 UnLua_ObjectMap -7

lua_pushvalue(L, -2);

index value index
8 METATABLE_UOBJECT -1
7 “Overridden” -2
6 METATABLE_UOBJECT -3
5 REQUIRED_MODULE -4
4 UserData-Object -5
3 LuaTable-INSTANCE -6
2 LightUserData-Object -7
1 UnLua_ObjectMap -8

lua_rawset(L, -4); //REQUIRED_MODULE[“Overridden”] = METATABLE_UOBJECT

index value index
6 METATABLE_UOBJECT -1
5 REQUIRED_MODULE -2
4 UserData-Object -3
3 LuaTable-INSTANCE -4
2 LightUserData-Object -5
1 UnLua_ObjectMap -6

lua_setmetatable(L, -2);

index value index
5 REQUIRED_MODULE -1
4 UserData-Object -2
3 LuaTable-INSTANCE -3
2 LightUserData-Object -4
1 UnLua_ObjectMap -3

lua_setmetatable(L, -3);

index value index
4 UserData-Object -1
3 LuaTable-INSTANCE -2
2 LightUserData-Object -3
1 UnLua_ObjectMap -4

lua_pop(L, 1);

index value index
3 LuaTable-INSTANCE -1
2 LightUserData-Object -2
1 UnLua_ObjectMap -3

lua_rawset(L, -3); 把栈顶的LuaTable-INSTANCE 作为值,把栈中倒数第二个位置的LightUserData-Object 作为键,设置到栈中倒数第三个位置的表UnLua_ObjectMap 中

index value index
1 UnLua_ObjectMap -1

lua_pop(L, 1); //清空了栈了

index value index
     

绑定lua的主要lua c api

    int FObjectRegistry::Bind(UObject* Object)
    {
        if (const auto Exists = ObjectRefs.Find(Object))
        {
            if (*Exists != LUA_NOREF)
                return *Exists;
        }

        const auto L = Env->GetMainState();

        int OldTop = lua_gettop(L);

        lua_getfield(L, LUA_REGISTRYINDEX, REGISTRY_KEY);
        lua_pushlightuserdata(L, Object);
        lua_newtable(L); // create a Lua table ('INSTANCE')
        PushObjectCore(L, Object); // push UObject ('RAW_UOBJECT')
        lua_pushstring(L, "Object");
        lua_pushvalue(L, -2);
        lua_rawset(L, -4); // INSTANCE.Object = RAW_UOBJECT

        // in some case may occur module or object metatable can 
        // not be found problem
        const auto Class = Object->IsA<UClass>() ? static_cast<UClass*>(Object) : Object->GetClass();
        const auto ClassBoundRef = Env->GetManager()->GetBoundRef(Class);
        int32 TypeModule = lua_rawgeti(L, LUA_REGISTRYINDEX, ClassBoundRef); // push the required module/table ('REQUIRED_MODULE') to the top of the stack
        int32 TypeMetatable = lua_getmetatable(L, -2); // get the metatable ('METATABLE_UOBJECT') of 'RAW_UOBJECT' 
        if (TypeModule != LUA_TTABLE || TypeMetatable == LUA_TNIL)
        {
            lua_pop(L, lua_gettop(L) - OldTop);
            return LUA_REFNIL;
        }

#if ENABLE_CALL_OVERRIDDEN_FUNCTION
        lua_pushstring(L, "Overridden");
        lua_pushvalue(L, -2);
        lua_rawset(L, -4);
#endif
        lua_setmetatable(L, -2); // REQUIRED_MODULE.metatable = METATABLE_UOBJECT
        lua_setmetatable(L, -3); // INSTANCE.metatable = REQUIRED_MODULE
        lua_pop(L, 1);

        lua_pushvalue(L, -1);
        const auto Ret = luaL_ref(L, LUA_REGISTRYINDEX);
        ObjectRefs.Add(Object, Ret);

        FUnLuaDelegates::OnObjectBinded.Broadcast(Object); // 'INSTANCE' is on the top of stack now

        lua_rawset(L, -3);
        lua_pop(L, 1);
        return Ret;
    }


参考

lua参考手册