蓝图的限制

蓝图的类型

以文档5.5为例:

https://dev.epicgames.com/documentation/en-us/unreal-engine/blueprint-variables-in-unreal-engine

相关的API https://clang.llvm.org/doxygen/group__CINDEX__TYPES.html

ref:

  • https://stackoverflow.com/questions/25231080/what-are-canonical-types-in-clang
  • usage: https://clang.llvm.org/docs/LibClang.html
    • https://clang.llvm.net.cn/docs/LibClang.html
  • example: https://blog.csdn.net/qq_45335399/article/details/113727548
// libclang_parser.cpp

// 定义递归访问函数

std::function<void(CXCursor)> visitNode = [&](CXCursor cursor) {

// 添加调试信息

CXCursorKind kind = clang_getCursorKind(cursor);

CXString cursor_kind_spelling = clang_getCursorKindSpelling(kind);

std::cout << "Cursor kind: " << clang_getCString(cursor_kind_spelling) << "\n";

clang_disposeString(cursor_kind_spelling);

  

// 处理所有声明和类型引用

if (clang_isDeclaration(kind) || clang_isReference(kind) || clang_isExpression(kind)) {

// 获取类型的完整字符串表示

CXType type = clang_getCursorType(cursor);

  

// 使用 CanonicalType 生成 key,和 cppast::to_string() 生成的格式一致

CXType canonical_type = clang_getCanonicalType(type);

CXString canonical_spelling = clang_getTypeSpelling(canonical_type);

std::string cppast_key = clang_getCString(canonical_spelling);

clang_disposeString(canonical_spelling);

  

// 同时获取 libclang 原始的类型字符串

CXString type_spelling = clang_getTypeSpelling(type);

std::string libclang_type = clang_getCString(type_spelling);

clang_disposeString(type_spelling);

  

// 获取 cursor 的“书写”,即它在源代码中的标识

CXString cursorSpelling = clang_getCursorSpelling(cursor);

std::cout <<"Cursor Spelling: " << clang_getCString(cursor_kind_spelling) << ": " << clang_getCString(cursorSpelling) << "\n";

  

CXCursor declCursor = clang_getTypeDeclaration(type);

if (!clang_Cursor_isNull(declCursor)) {

CXString typedefName = clang_getCursorSpelling(declCursor);

std::string source_typedef_name = clang_getCString(typedefName);

clang_disposeString(typedefName);

std::cout << "Typedef spelling as in source: " << source_typedef_name << std::endl;

}

std::cout << "Generated key: " << cppast_key << "\n";

std::cout << "Original libclang type: " << libclang_type << "\n" << std::endl;

if (!libclang_type.empty() && !cppast_key.empty()) {

pimpl_->type_spelling_map[cppast_key] = libclang_type;

}

}

  

// 递归访问子节点

clang_visitChildren(cursor,

[](CXCursor child, CXCursor parent, CXClientData client_data) {

auto& visit = *reinterpret_cast<std::function<void(CXCursor)>*>(client_data);

visit(child);

return CXChildVisit_Continue;

},

&visitNode);

};

// 从根节点开始访问

CXCursor root = clang_getTranslationUnitCursor(tu.get());
visitNode(root);


// output result

Cursor kind: CXXMethod

Cursor Spelling: CXXMethod: preloadChannel

Typedef spelling as in source:

Generated key: int (const char *, const char *, unsigned int)

Original libclang type: int (const char *, const char *, agora::rtc::uid_t)

  

Cursor kind: ParmDecl

Cursor Spelling: ParmDecl: token

Typedef spelling as in source:

Generated key: const char *

Original libclang type: const char *

  

Cursor kind: ParmDecl

Cursor Spelling: ParmDecl: channelId

Typedef spelling as in source:

Generated key: const char *

Original libclang type: const char *

  

Cursor kind: ParmDecl

Cursor Spelling: ParmDecl: uid

Typedef spelling as in source: uid_t

Generated key: unsigned int

Original libclang type: agora::rtc::uid_t

  

Cursor kind: TypeRef

Cursor Spelling: TypeRef: agora::rtc::uid_t

Typedef spelling as in source: uid_t

Generated key: unsigned int

Original libclang type: agora::rtc::uid_t
  1. 建立转换体系 Cpp 与 BP 的转换,本身转换体系应该建立在Type 上。 也就是SimpleType 带了包含 Cpp - > BP 与 BP -> Cpp 的信息

  2. 根据filename 要做指定的拆分。

    1. cxxfile 中 node 中包含不同的nodes

实现思路

  1. 目前你搭好了框架,先大概编译了一次看到了大致的Error
    1. 其中,有错先手动修复,让整个流程得以推进
    2. 如果只有一个文件有错,可以先本地修改生成后替换
  2. 然后开始缩小样本:
    1. 因为如果是全量去生成:
      1. 比如 UStruct FString 默认值设为了0 ,有很多地方都有这个错误,你不方便全部修改,一个个改会浪费很多时间。你会只在同一个旧问题里面花费更多的时间。
    2. 用最少的样本先通过编译可以跑起来,然后再一点点扩展新的问题。

History

  1. 第一版: 一开始是尝试直接 bp 与 cpp 的 Enum 之间 static_cast 但是发现由于BP的Enum 都是uint8 (0~255)
  2. 第二版: 针对值不一样的Enum,包成一个Wrapper 单独进行Remmaping 重新映射
  3. 第三版:由于发现BP 首项是需要 等于0的,当时方式是如果不是从0开始的enum 单独添加
INVALID_OPT_BPGEN_NULL UMETA(Hidden, DisplayName = "AGORA NULL VALUE")
这个有这么几个坏处:首先是这一项虽然可以Hidden 但是还是会某些地方成为Default,而且这个值不对应Agora 的任何值,万一用户选择了,就不知道对应什么
  1. 第四版:Agora 的 Enum 都做一个Remmaping ,并且通过预先定义的Macro 函数去预先写好生成的模板 GEN_UABTFUNC_SIGNATURE_ENUMCONVERSION_4_ENTRIES:
    1. 实现上是预先定义ToRawValue 与 WrapWithUEEnum
    2. 但是这也有问题:就是这个Macro 目前的Entries 数量是要先定义好的,Agora 有些Enum 是有超过70个的,每个Macro 函数都写一个感觉也比较费
    3. 使用模板函数呢,也有一个问题,那就是可能会有Enum 值存在一样的情况 4.主要是当时为了好写:用的是Switch ,但是Switch 本身Case 不能有重复项
// 打个比方:
enum class VIDEO_SOURCE_TYPE : uint8{

	VIDEO_SOURCE_CAMERA_PRIMARY = 0,
	// VIDEO_SOURCE_CAMERA = VIDEO_SOURCE_CAMERA_PRIMARY,
	VIDEO_SOURCE_CAMERA,
	
	VIDEO_SOURCE_CAMERA_SECONDARY,
	
	VIDEO_SOURCE_SCREEN_PRIMARY,
	//VIDEO_SOURCE_SCREEN = VIDEO_SOURCE_SCREEN_PRIMARY,
	VIDEO_SOURCE_SCREEN,
	//....
	
};

Terra

目前Terra 版本:

  1. 由于Enum 77 太多了,所以去掉了使用Macro Function 做模板, 直接让Terra 生成原来的函数
  2. 由于存在相同的enum 值,所以改为if-else

这里注意Category 是必须要的,因为是Editor 需要一个分类


USTRUCT(BlueprintType)

struct FUABT_UserInfo

{

GENERATED_BODY()

public:

UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Agora|UserInfo")

int64 uid = 0;

}

};

How to resolve: FUABT macro scope problem

USTRUCT 没法包在Macro Scope 内

#if PLATFORM_WINDOWS

USTRUCT(BlueprintType)
struct FUABT_Test{
	GENERATED_BODY()
public:

}

#endif

Error: USTRUCT must not be inside preprocessor blocks, except for WITH_EDITORONLY_DATA

TArray UCLASS 需要是Pointer Ref: https://forums.unrealengine.com/t/missing-in-expected-a-pointer-type/312141

采用CBExecutor 实现的方式

DECLARE_DYNAMIC_MULTICAST 这个还是放在类内

在外面可能会有一些奇怪的bug ,导致这个类有的能生成,有的不能生成 影响编译结果的判断 主要是数量太多,会影响后续的判断 放里面 从 572 -> 118

希望是给一个Cpp Type 就可以生成 BP Type 包含如何转换的信息

基础思路

  1. 基础的正则匹配
  2. 配合WhiteList / Blacklist 修正
    1. 目前发现,有些匹配不仅仅是key - Val ,也需要正则匹配

ConvertToBP

  1. 使用type.name 还是type.source 作为key 带不带 namespace 以及 const 相关的一些东西

Issues

  • UE 425 不认Byte (待验证)

数组 - TArray 的问题

怎么解决继承问题 IAudioFrameObserver 继承自 IAudioFrameObserverBase

但是node 只有 新增的method

怎么解决:Custom Header 带来的函数不匹配的问题

其他的框架是: 手写的 extends 生成的 会有手写的method override 生成的method

CBExecutor: 就是CBExecutor 本身不能有return value 因为DECALRE_DYNAMIC_MULTICAST_DELEGATE 生成的函数就没有return 值

OnLocalAudioSpectrum.AddDynamic(Executor, &UAgoraBPuAudioSpectrumObserverCBExecutor::OnLocalAudioSpectrum); OnLocalAudioSpectrum 本身会有return value AddDynamic 的绑定无法有return value