此一回主要研究虚幻的反射类。
虚幻的反射类跟C++的静态编译类不同,可以在Runtime增删改,其概念更接近LUA的table。
UClass实质功能就是描述一段内存的内容,使用FProperty描述成员变量,UFunction描述反射函数。
FProperty描述了成员变量在Container(即所属的类UClass)偏移量和大小,使用链表记录所有成员变量。
成员变量的FProperty需要注意POD(简单理解,不涉及内部内存分配和虚函数的结构体/类,int32/FVector等),
对于POD比较简单,变量直接复制到对应地址即可,但是对于非POD必须调用专用赋值函数,FProperty::SetPropertyValue_InContainer(),
该函数同时适用POD和非POD,尽量避免直接赋值操作。
UFunction描述了蓝图函数,UFunction内部使用FProperty记录函数参数以及返回值,使用TMap记录。
UClass的UFunction列表包含了蓝图文件中定义的函数和C++编写的反射函数,对于UFunction的调用一般是通过UObject::ProcessEvent()函数触发的。
其中蓝图编写的函数通过虚幻脚本虚拟机执行;C++编写的反射函数间接调用一个FNativeFuncPtr类型的全局函数最终调用C++函数本体,
具体是在给UFUnction加标记FUNC_Native,同时设置一个全局函数:NewFunc->SetNativeFunc(CallLuaFunction)。
// Fill out your copyright notice in the Description page of Project Settings.
#include "LuaGeneratedClass.h"
#include "lua/lua.hpp"
#include "FastLuaHelper.h"
#include "LuaFunction.h"
#include "LuaObjectWrapper.h"
static int32 FillPropertyIntoField_Class(UField* InOutField, TMap<FString, int32>& InKV)
{
int32 PropCount = 0;
int32 PropOffset = 0;
EObjectFlags ObjectFlags = EObjectFlags::RF_Standalone;
EPropertyFlags PropertyFlags = EPropertyFlags::CPF_None;
bool bIsPOD = true;
for (const TPair<FString, int32>& It : InKV)
{
FProperty* NewProp = nullptr;
UE4CodeGen_Private::EPropertyGenFlags PropertyType = (UE4CodeGen_Private::EPropertyGenFlags)(It.Value);
switch (PropertyType)
{
case UE4CodeGen_Private::EPropertyGenFlags::Int8:
NewProp = new FInt8Property(InOutField, FName(*It.Key), ObjectFlags, PropOffset, PropertyFlags);
NewProp->SetPropertyFlags(EPropertyFlags::CPF_IsPlainOldData);
break;
case UE4CodeGen_Private::EPropertyGenFlags::Byte:
NewProp = new FByteProperty(InOutField, FName(*It.Key), ObjectFlags, PropOffset, PropertyFlags, nullptr);
NewProp->SetPropertyFlags(EPropertyFlags::CPF_IsPlainOldData);
break;
case UE4CodeGen_Private::EPropertyGenFlags::Int16:
NewProp = new FInt16Property(InOutField, FName(*It.Key), ObjectFlags, PropOffset, PropertyFlags);
NewProp->SetPropertyFlags(EPropertyFlags::CPF_IsPlainOldData);
break;
case UE4CodeGen_Private::EPropertyGenFlags::UInt16:
NewProp = new FUInt16Property(InOutField, FName(*It.Key), ObjectFlags, PropOffset, PropertyFlags);
NewProp->SetPropertyFlags(EPropertyFlags::CPF_IsPlainOldData);
break;
case UE4CodeGen_Private::EPropertyGenFlags::Int:
NewProp = new FIntProperty(InOutField, FName(*It.Key), ObjectFlags, PropOffset, PropertyFlags);
NewProp->SetPropertyFlags(EPropertyFlags::CPF_IsPlainOldData);
break;
case UE4CodeGen_Private::EPropertyGenFlags::UInt32:
NewProp = new FUInt32Property(InOutField, FName(*It.Key), ObjectFlags, PropOffset, PropertyFlags);
NewProp->SetPropertyFlags(EPropertyFlags::CPF_IsPlainOldData);
break;
case UE4CodeGen_Private::EPropertyGenFlags::Int64:
NewProp = new FInt64Property(InOutField, FName(*It.Key), ObjectFlags, PropOffset, PropertyFlags);
NewProp->SetPropertyFlags(EPropertyFlags::CPF_IsPlainOldData);
break;
case UE4CodeGen_Private::EPropertyGenFlags::UInt64:
NewProp = new FUInt64Property(InOutField, FName(*It.Key), ObjectFlags, PropOffset, PropertyFlags);
NewProp->SetPropertyFlags(EPropertyFlags::CPF_IsPlainOldData);
break;
case UE4CodeGen_Private::EPropertyGenFlags::Float:
NewProp = new FFloatProperty(InOutField, FName(*It.Key), ObjectFlags, PropOffset, PropertyFlags);
NewProp->SetPropertyFlags(EPropertyFlags::CPF_IsPlainOldData);
break;
case UE4CodeGen_Private::EPropertyGenFlags::Str:
NewProp = new FStrProperty(InOutField, FName(*It.Key), ObjectFlags, PropOffset, PropertyFlags);
bIsPOD = false;
break;
case UE4CodeGen_Private::EPropertyGenFlags::Array:
NewProp = new FArrayProperty(InOutField, FName(*It.Key), ObjectFlags, PropOffset, PropertyFlags, EArrayPropertyFlags::None);
bIsPOD = false;
break;
case UE4CodeGen_Private::EPropertyGenFlags::Map:
NewProp = new FMapProperty(InOutField, FName(*It.Key), ObjectFlags, PropOffset, PropertyFlags, EMapPropertyFlags::None);
bIsPOD = false;
break;
}
++PropCount;
PropOffset += NewProp->GetSize();
}
return PropCount;
}
static void CallLuaFunction(UObject* Context, FFrame& TheStack, RESULT_DECL)
{
UClass* TmpClass = Context->GetClass();
if (ULuaGeneratedClass* LuaClass = Cast<ULuaGeneratedClass>(TmpClass))
{
lua_State* LuaState = LuaClass->GetLuaState();
int32 LuaTableIndex = LuaClass->GetLuaTableIndex();
FString FuncName = TheStack.Node->GetName();
lua_rawgeti(LuaState, LUA_REGISTRYINDEX, LuaTableIndex);
int32 LuaType = lua_type(LuaState, -1);
lua_getfield(LuaState, -1, TCHAR_TO_UTF8(*FuncName));
if (lua_isfunction(LuaState, -1))
{
int32 ParamsNum = 0;
FProperty* ReturnParam = nullptr;
//store param from UE script VM stack
FStructOnScope FuncTmpMem(TheStack.Node);
//push self
FLuaObjectWrapper::PushObject(LuaState, Context);
++ParamsNum;
for (TFieldIterator<FProperty> It(TheStack.Node); It; ++It)
{
//get function return Param
FProperty* CurrentParam = *It;
void* LocalValue = CurrentParam->ContainerPtrToValuePtr<void>(FuncTmpMem.GetStructMemory());
TheStack.StepCompiledIn<FProperty>(LocalValue);
if (CurrentParam->HasAnyPropertyFlags(CPF_ReturnParm))
{
ReturnParam = CurrentParam;
}
else
{
//set params for lua function
FastLuaHelper::PushProperty(LuaState, CurrentParam, FuncTmpMem.GetStructMemory(), 0);
++ParamsNum;
}
}
//call lua function
int32 CallRet = lua_pcall(LuaState, ParamsNum, ReturnParam ? 1 : 0, 0);
if (CallRet)
{
UE_LOG(LogTemp, Warning, TEXT("%s"), UTF8_TO_TCHAR(lua_tostring(LuaState, -1)));
}
if (ReturnParam)
{
//get function return Value, in common
FastLuaHelper::FetchProperty(LuaState, ReturnParam, FuncTmpMem.GetStructMemory(), -1);
}
}
}
}
int32 ULuaGeneratedClass::GenerateClass(struct lua_State* InL)
{
FString InClassName = FString(UTF8_TO_TCHAR(lua_tostring(InL, 1)));
FString SuperClassName = FString(UTF8_TO_TCHAR(lua_tostring(InL, 2)));
UClass* SuperClass = FindObject<UClass>(ANY_PACKAGE, *SuperClassName);
if (!SuperClass)
{
SuperClass = UObject::StaticClass();
}
ULuaGeneratedClass* NewClass = FindObject<ULuaGeneratedClass>(ANY_PACKAGE, *InClassName);
if (NewClass)
{
NewClass->RemoveFromRoot();
NewClass->MarkPendingKill();
NewClass = nullptr;
}
NewClass = NewObject<ULuaGeneratedClass>(GetTransientPackage(), *InClassName, RF_Public | RF_Standalone | RF_Transient);
NewClass->SetSuperStruct(SuperClass);
NewClass->ClassFlags |= CLASS_Native;
NewClass->AddToRoot();
TMap<FString, int32> TmpKV;
if (lua_istable(InL, 3))
{
{
lua_pushvalue(InL, 3);
NewClass->RegistryIndex = luaL_ref(InL, LUA_REGISTRYINDEX);
NewClass->LuaState = InL;
}
lua_pushnil(InL);
while (lua_next(InL, 3))
{
int32 KeyType = lua_type(InL, -2);
int32 ValueType = lua_type(InL, -1);
if (KeyType == LUA_TSTRING && ValueType == LUA_TFUNCTION)
{
FString TmpFunctionName = UTF8_TO_TCHAR(lua_tostring(InL, -2));
UFunction* NewFunc = nullptr;
UFunction* SuperFunction = SuperClass->FindFunctionByName(*TmpFunctionName);
if (SuperFunction)
{
NewFunc = Cast<UFunction>(StaticDuplicateObject(SuperFunction, NewClass, *TmpFunctionName));
if (NewFunc)
{
NewFunc->ParmsSize = SuperFunction->ParmsSize;
NewFunc->PropertiesSize = SuperFunction->PropertiesSize;
NewFunc->SetNativeFunc(CallLuaFunction);
NewFunc->FunctionFlags |= FUNC_Native;
NewClass->AddFunctionToFunctionMap(NewFunc, *TmpFunctionName);
}
}
}
lua_pop(InL, 1);
}
lua_getfield(InL, 3, "VariableList");
if (lua_istable(InL, -1))
{
lua_pushnil(InL);
while (lua_next(InL, -2))
{
TPair<FString, int32> TmpNew;
TmpNew.Key = FString(UTF8_TO_TCHAR(lua_tostring(InL, -2)));
TmpNew.Value = lua_tointeger(InL, -1);
TmpKV.Add(TmpNew);
lua_pop(InL, 1);
}
}
lua_pop(InL, 1);
FillPropertyIntoField_Class(NewClass, TmpKV);
}
NewClass->Bind();
NewClass->StaticLink(true);
NewClass->AssembleReferenceTokenStream();
// Ensure the CDO exists
NewClass->GetDefaultObject();
FLuaObjectWrapper::PushObject(InL, NewClass);
return 1;
}
bool ULuaGeneratedClass::IsFunctionImplementedInScript(FName InFunctionName) const
{
if (RegistryIndex > 0)
{
lua_rawgeti(LuaState, LUA_REGISTRYINDEX, RegistryIndex);
lua_getfield(LuaState, -1, TCHAR_TO_UTF8(*InFunctionName.ToString()));
return lua_isfunction(LuaState, -1);
}
return Super::IsFunctionImplementedInScript(InFunctionName);
}
在LUA中使用
--不要在MyActor表内部记录SuperClass名字
--方便修改父类
local MyActor = MyActor or {}
MyActor.VariableList =
{
['MyID'] = UnrealPropertyType.Int,
['MyName'] = UnrealPropertyType.Str,
}
function MyActor:ReceiveBeginPlay()
self.MyName = 'MyGame'
print('ReceiveBeginPlay')
local TmpMesh = Unreal.LuaLoadObject(self, '/Game/ParagonShinbi/Characters/Heroes/Shinbi/Meshes/Shinbi.Shinbi')
self.Mesh:SetSkeletalMesh(TmpMesh)--可以看见场景中Actor的模型变了
local AnimBP = Unreal.LuaLoadObject(self, '/Game/ParagonShinbi/Characters/Heroes/Shinbi/Shinbi_AnimBlueprint.Shinbi_AnimBlueprint')
self.Mesh:SetAnimClass(AnimBP)
end
function MyActor:ReceiveEndPlay(InReason)
print('ReceiveEndPlay:' .. tostring(InReason))
print(self.MyName)
end
function MyActor:ReceiveActorBeginOverlap(InActor)
print(tostring(InActor))
end
function DelayInit()
local PlayerCtrl = GameplayStatics:GetPlayerController(G_GameInstance, 0)
G_LocalPlayer = GenericGameAPI:GetLocalPlayer(PlayerCtrl)
local WorldName = GameplayStatics:GetCurrentLevelName(G_GameInstance, true)
--新建一个反射类MyActor,继承自ACharacter
local MyCls = Unreal.LuaGenerateClass('MyActor', 'Character', MyActor)
local TmpVec = {X = 5250, Y = 8740, Z = 170}
local TmpTrans = Unreal.LuaNewStruct('Transform')
TmpTrans.Translation = Unreal.LuaNewStruct('Vector', TmpVec)
local ActorInst = GameplayStatics:BeginSpawningActorFromClass(G_GameInstance, MyCls, TmpTrans)
GameplayStatics:FinishSpawningActor(ActorInst, TmpTrans)
end
续:
虚幻引擎内部仅存在一个脚本虚拟机实例,而LUA中可能需要同时存在多个虚拟机,
执行创建反射类的代码可能会被多个LUA虚拟机反复执行,导致虚幻脚本虚拟机中只会保留一个反射类,
并且关联到最后一次执行创建到LUA虚拟机,我想这大概是UnLua插件暂不支持多状态的一个重要原因。
在最终游戏客户端中一个LUA虚拟机也许够用,但在开发阶段以及服务器端非常依赖多个LUA虚拟机。
临时方案:
如果能保持创建反射类的LUA代码跟其他逻辑代码分离,那那么可以专门使用一个静态成员LUA状态机处理反射枚举/结构体/类的创建,
不过这个应该很难做到。