蓝图的限制
蓝图的类型
以文档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
-
建立转换体系 Cpp 与 BP 的转换,本身转换体系应该建立在Type 上。 也就是SimpleType 带了包含 Cpp - > BP 与 BP -> Cpp 的信息
-
根据filename 要做指定的拆分。
- cxxfile 中 node 中包含不同的nodes
实现思路
- 目前你搭好了框架,先大概编译了一次看到了大致的Error
- 其中,有错先手动修复,让整个流程得以推进
- 如果只有一个文件有错,可以先本地修改生成后替换
- 然后开始缩小样本:
- 因为如果是全量去生成:
- 比如 UStruct FString 默认值设为了0 ,有很多地方都有这个错误,你不方便全部修改,一个个改会浪费很多时间。你会只在同一个旧问题里面花费更多的时间。
- 用最少的样本先通过编译可以跑起来,然后再一点点扩展新的问题。
- 因为如果是全量去生成:
History
- 第一版: 一开始是尝试直接 bp 与 cpp 的 Enum 之间 static_cast 但是发现由于BP的Enum 都是uint8 (0~255)
- 第二版: 针对值不一样的Enum,包成一个Wrapper 单独进行Remmaping 重新映射
- 第三版:由于发现BP 首项是需要 等于0的,当时方式是如果不是从0开始的enum 单独添加
INVALID_OPT_BPGEN_NULL UMETA(Hidden, DisplayName = "AGORA NULL VALUE")
这个有这么几个坏处:首先是这一项虽然可以Hidden 但是还是会某些地方成为Default,而且这个值不对应Agora 的任何值,万一用户选择了,就不知道对应什么
- 第四版:Agora 的 Enum 都做一个Remmaping ,并且通过预先定义的Macro 函数去预先写好生成的模板 GEN_UABTFUNC_SIGNATURE_ENUMCONVERSION_4_ENTRIES:
- 实现上是预先定义ToRawValue 与 WrapWithUEEnum
- 但是这也有问题:就是这个Macro 目前的Entries 数量是要先定义好的,Agora 有些Enum 是有超过70个的,每个Macro 函数都写一个感觉也比较费
- 使用模板函数呢,也有一个问题,那就是可能会有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 版本:
- 由于Enum 77 太多了,所以去掉了使用Macro Function 做模板, 直接让Terra 生成原来的函数
- 由于存在相同的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 包含如何转换的信息
基础思路
- 基础的正则匹配
- 配合WhiteList / Blacklist 修正
- 目前发现,有些匹配不仅仅是key - Val ,也需要正则匹配
ConvertToBP
- 使用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