Browse Source

feat: 添加 CustomThunk 使用

nicetry12138 1 year ago
parent
commit
1e5c781bbd
1 changed files with 127 additions and 0 deletions
  1. 127 0
      UE5/引擎开发记录/README.md

+ 127 - 0
UE5/引擎开发记录/README.md

@@ -0,0 +1,127 @@
+# 引擎开发记录
+
+## CustomChunk
+
+在计算机编程中,`Thunk` 是一个特殊的函数,其主要目的是延迟计算或调用,将一个多参数函数转换为一个单参数的延迟执行版本
+
+在 `Unreal` 中 `Thunk` 用于将蓝图中的事件或者函数调用转换为 C++ 中的函数调用。 Thunk 函数充当中间层,确保蓝图和 C++ 之间的通信和数据传递能够正常运行
+
+`CustomThunk` 顾名思义就是自定义 `Thunk` 函数,用于告诉引擎这个函数不是自动生成的,而是开发者提供的
+
+这种自定义的 `Thunk` 函数允许开发者直接与**蓝图虚拟机的堆栈**进行交互,从而可以处理更复杂的逻辑,比如**动态参数类型**的处理、**泛型编程**或者在运行时进行特定的**内存操作**
+
+CustomThunk 定义的函数,通常需要通过 `DECLARE_FUNCTION` 宏中以 `exec` 前缀来声明。这个 `exec` 是需要遵守的约定,因为 `Unreal` 的蓝图虚拟机在处理函数调用时,会查找以 `exec` 为前缀的函数来执行,相当于引擎内部的约定
+
+声明 `CustomThunk` 函数需要使用 `DECLARE_FUNCTION`,定义函数使用宏 `DEFINE_FUNCTION`
+
+```cpp
+// .h 文件
+UFUNCTION(BlueprintCallable, CustomThunk, Category = "Utilities|Instanced Struct", meta = (CustomStructureParam = "Value", BlueprintInternalUseOnly="true", NativeMakeFunc))
+static FInstancedStruct MakeInstancedStruct(const int32& Value);
+DECLARE_FUNCTION(execMakeInstancedStruct);
+
+// .cpp 文件
+FInstancedStruct UStructUtilsFunctionLibrary::MakeInstancedStruct(const int32& Value)
+{
+	// We should never hit this! stubs to avoid NoExport on the class.
+	checkNoEntry();
+	return {};
+}
+
+DEFINE_FUNCTION(UStructUtilsFunctionLibrary::execMakeInstancedStruct)
+{
+    // 
+}
+```
+
+> `CustomStructureParam` 允许名为 `Value` 的参数接受多种不同类型的数据
+> `AutoCreateRefTerm` 会自动为标记为通配符的结构体参数创建引用术语
+
+通过蓝图虚拟机的堆栈可以获得函数参数等信息
+
+```cpp
+DEFINE_FUNCTION(UStructUtilsFunctionLibrary::execMakeInstancedStruct)
+{	
+	Stack.MostRecentPropertyAddress = nullptr;	
+	Stack.MostRecentPropertyContainer = nullptr;
+	Stack.StepCompiledIn<FStructProperty>(nullptr);	// 从蓝图虚拟机的堆栈中获取下一个 FStructProperty 类型的属性,这通常是函数的参数
+	
+	const FStructProperty* ValueProp = CastField<FStructProperty>(Stack.MostRecentProperty);	// 尝试将获取到的属性转换为 FStructProperty 类型,这代表结构体
+	const void* ValuePtr = Stack.MostRecentPropertyAddress;			// 获取目标结构体的地址
+
+	// ...
+}
+```
+
+如果函数存在多个参数,那就需要多次调用 `Stack.StepCompiledIn` 直到参数全部获取,当然也 Unreal 也提供了一些宏来方便的获取参数
+
+以 `UAnimationAttributeBlueprintLibrary::execSetAttributeKey` 为例
+
+```cpp
+UFUNCTION(BlueprintCallable, CustomThunk, Category = "Animation|Attributes", meta=(CustomStructureParam="Value", BlueprintInternalUseOnly="true"))
+static bool SetAttributeKey(TScriptInterface<IAnimationDataController> AnimationDataController, const FAnimationAttributeIdentifier& AttributeIdentifier, float Time, const int32& Value);
+
+DEFINE_FUNCTION(UAnimationAttributeBlueprintLibrary::execSetAttributeKey)
+{
+	P_GET_TINTERFACE(IAnimationDataController, AnimationDataController);
+	P_GET_STRUCT_REF(FAnimationAttributeIdentifier, AttributeIdentifier);
+	P_GET_PROPERTY(FFloatProperty, TimeInterval);
+
+	Stack.MostRecentProperty = nullptr;
+	Stack.MostRecentPropertyAddress = nullptr;
+	
+	Stack.StepCompiledIn<FStructProperty>(nullptr);
+	
+	const FStructProperty* ItemProperty = CastField<FStructProperty>(Stack.MostRecentProperty);
+	void* ItemDataPtr = Stack.MostRecentPropertyAddress;
+
+	// ...
+}
+```
+
+由于函数的前几个参数明确了类型 `IAnimationDataController`、`FAnimationAttributeIdentifier`、`float`,分别使用 `P_GET_TINTERFACE`、`P_GET_STRUCT_REF` 和 `P_GET_PROPERTY` 来获取对应的数据
+
+> `P_GET_TINTERFACE` 获取接口(Interface)类型属性
+> `P_GET_STRUCT_REF` 获取结构体(STRUCT)类型属性
+
+最后一个传递的是 `CustomStructureParam` 标记的参数,可以接受任意类型,这里期望的是一个结构体,所以使用 `FStructProperty` 来接受,由于提前不知道结构体的类型,所以不能使用 `P_GET_STRUCT_REF`
+
+根据 `FStructProperty` 可以得到结构体的属性列表和值,进而可以根据需要进行后续操作
+
+```cpp
+DEFINE_FUNCTION(UJsonBlueprintFunctionLibrary::execGetField)
+{
+	P_GET_STRUCT_REF(FJsonObjectWrapper, JsonObject);
+	P_GET_PROPERTY(FStrProperty, FieldName);
+
+	Stack.StepCompiledIn<FProperty>(nullptr);
+	FProperty* ValueProp = Stack.MostRecentProperty;
+	void* ValuePtr = Stack.MostRecentPropertyAddress;
+
+	P_FINISH;
+
+	if (!ValueProp || !ValuePtr)
+	{
+		// 错误警告
+	}
+
+	bool bResult;
+	if (FieldName.IsEmpty())
+	{
+		// 特殊处理
+	}
+
+	P_NATIVE_BEGIN
+	bResult = JsonFieldToProperty(FieldName, JsonObject, ValueProp, ValuePtr);
+	P_NATIVE_END
+
+	*static_cast<bool*>(RESULT_PARAM) = bResult;
+}
+```
+
+继续以 `execMakeInstancedStruct` 为例,需要注意的是 `P_FINISH`、`P_NATIVE_BEGIN`、`P_NATIVE_END` 和 `RESULT_PARAM`
+
+- `P_FINISH`: 宏之前为获取函数参数的代码块
+- `P_NATIVE_BEGIN` 和 `P_NATIVE_END` : 宏包裹着真正执行该函数功能的被调用函数,若是泛型蓝图节点,此处调用该函数的泛型版本(泛型函数)
+- `RESULT_PARAM`: 表示函数返回值
+