运行时创建UField[叁]

此一回主要研究虚幻的反射类。
虚幻的反射类跟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状态机处理反射枚举/结构体/类的创建,
不过这个应该很难做到。

原文地址:https://www.cnblogs.com/rpg3d/p/12627460.html