动态绑定
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;
}