Game Engine MetaData Creation With Clang
作者:互联网
A Little Context to Start
我的hobby引擎使用一个系统,任何类或者结构体可以有metadata,但是这不是严格必须的。
除此之外,每个metadata开启的类型,并不要求去有一个虚函数表。让我们考虑一个简单的类型,
它位于一个名为ChildType.h的头文件中。
//ChildType.h
class childType : public parentType
{
DECLARE_TYPE( childType, parentType );
void RedactedFunction( float secretSauce );
float unsavedValue; /// +NotSaved
double * ptrToDbl;
ValueType_t someValues[3];
};
通过使用DECLARE_TYPE宏的美德,它清晰地表示这个类型目的是开启metadata,但是它不能很好地知道这个数据来自于哪里。
//ChildTypeClass.cpp
MemberMetadata const childType::TypeMemberInfo[] = {
{“unsavedValue”, eDT_Float, eDT_None, eDF_NotSaved, offsetof(childType, unsavedValue), 1, NULL },
{“ptrToDbl”, eDT_Pointer, eDT_Float64, eDF_None, offsetof(childType, ptrToDbl), 1, NULL },
{“someValues”, eDT_StaticArray, eDT_Struct, eDF_None, offsetof(childType, someValues), 3, ValueStructClass }
};
IMPLEMENT_TYPE( childType, parentType );
DECLARE_TYPE宏添加了MemberMetadata结构体地类静态数组。
Enter Clang
Clang是一个C语言家族前端,对于LLVM。Clang是一个C++对于LLVM编译器。
libClang提供了一个相对简单的C风格的API,到抽象语法树(AST),被语言解析器所创建。
A Little Glossary Action
libClang API使用一些简单的概念,去建模你的代码,在AST形式下。
Translation Unity
实际上,它是创建一个AST的编译器的一次运行。我们可以思考它作为一个编译的文件+任何它包含的头文件。
在libClang的术语,它是一个基础层次的容器,并且是我们需要进行的数据收集工作的起点。
Cursor
一个Cursor表示一个语法构造,在AST中。它们可以表示一个完整的命名空间或者一个简单的变量名。
它们也保留父子/儿子关系,和其它cursors。对于我们的目的,cursors是在AST中的节点,我们需要去遍历的。
它们也包含引用到源文件位置,它们所找到的。
Type
这个是非常简单的。我们查看的cursors将经常引用一个类型。这些就是字面上的语言类型。
记在心里,Clang建模了整个类型系统。
The Plan
一旦解析完成,整个解决方案就非常轻松了。
1.设置环境
2.对于每个头文件,制作一个Translation Unit。
3.遍历AST,对于感兴趣的类型定义cursors。
4.对于每个类型定义,查找member data cursors,和其它信息。
5.一旦我们获得了关于类型的所有信息,就将文本转储到文件中。
Setup-Compiler Environment
我们需要使用相同的命令行预处理器定义(-D)就像同样的额外包含文件夹(-I)。
Setup-Header Environment
...
Time to Make the Doughnuts
当我们收集了所有的头文件路径,和预处理符号,调用clang非常轻松。
我们不可以只传递header到clang_parseTranslationUnit并且调用它。Clang不会知道如何去执行,当它不知道额外的参数的时候,去表示语言去使用。我也需要去包含PCH文件。
Clang支持内存中未保存的文件。
这里有一个基本步骤,用于构建一个translation unit,对于一个单一的头文件,叫做"MyEngineHeader.h"。
char const * args[] = {"-Wmicrosoft"
, "-Wunknown-pragmas"
, "-I\\MyEngine\\Src"
, "-I\\MyEngine\\Src\\Core"
, "-D_DEBUG=1" };
CXUnsavedFile dummyFile;
dummyFile.Filename = "dummy.cpp";
dummyFile.Contents = "#include \"MyEnginePCH.h\"\n#include \"MyEngineHeader.h\"";
dummyFile.Length = strlen( dummyFile.Contents );
CXIndex CIdx = clang_createIndex(1, 1);
CXTranslationUnit tu = clang_parseTranslationUnit( CIdx, "dummy.cpp"
, args, ARRAY_SIZE(args)
, &dummyFile, 1
, CXTranslationUnit_None );
使用clang_getDiagnostic,clang_getDiagnosticSpelling,去获取人类可读的错误消息。
unsigned int numDiagnostics = clang_getNumDiagnostics( tu );
for ( unsigned int iDiagIdx=0; iDiagIdx < numDiagnostics; ++iDiagIdx )
{
CXDiagnostic diagnostic = clang_getDiagnostic( tu, iDiagIdx );
CXString diagCategory = clang_getDiagnosticCategoryText( diag );
CXString diagText = clang_getDiagnosticSpelling( diag );
CXDiagnosticSeverity severity = clang_getDiagnosticSeverity( diag );
printf( "Diagnostic[%d] - %s(%d)- %s\n"
, iDiagIdx
, clang_getCString( diagCategory )
, severity
, clang_getCString( diagText ) );
clang_disposeString( diagText );
clang_disposeString( diagCategory );
clang_disposeDiagnostic( diagnostic );
}
Time to Start Digging!
编译阶段应当已经提供你一个有效的translation unit。当我们获取了顶层的top-level cursor,使用clang_getTranslationUnitCursor(),我们将translation unit放在一个安全的位置,并且使用cursor作为高层的对象。
C风格的Clang接口使用一个笨拙的回调API,叫做clang_visitChildren。Clang会调用你的回调,对于你的每个child cursor,它碰见的。你的回调,返回一个值,表示迭代器是否应当重复使用更深的儿子。
我们在这个阶段只对类型声明感兴趣,但是C++允许新的类型发生,在一些地方。
还有一些没有覆盖,比如函数私有的类型,还有union。
遍历类型:
MyTraversalContext typeTrav;
clang_visitChildren( clang_getTranslationUnitCursor( tu ), GatherTypesCB, &typeTrav );
enum CXChildVisitResult GatherTypesCB( CXCursor cursor, CXCursor parent, CXClientData client_data )
{
MyTraversalContext * typeTrav = reinterpret_cast( client_data );
CXCursorKind kind = clang_getCursorKind( cursor );
CXChildVisitResult result = CXChildVisit_Continue;
switch( kind )
{
case CXCursor_EnumConstantDecl:
typeTrav->AddEnumCursor( cursor );
break;
case CXCursor_StructDecl:
case CXCursor_ClassDecl:
typeTrav->AddNewTypeCursor( cursor );
result = CXChildVisit_Recurse;
break;
case CXCursor_TypedefDecl:
case CXCursor_Namespace:
result = CXChildVisit_Recurse;
break;
}
return result;
}
枚举暂时看成整数吧。
Panning For Gold
我们现在有了一大串类型,我们想去过滤它们。
我们可以迭代我们之前步骤创建的type cursors,感兴趣的类型列表,并且询问每个,它们来自于哪个文件。
类型来自于其它文件将被安全地剔除。
Data Gathering-Internal
基础类型还有成员变量应当是清晰的,但是静态类成员可能怪异的,对于这个列表。
Data Gathering-External
...
标签:Engine,childType,Creation,Clang,cursor,Game,CXCursor,clang,类型 来源: https://www.cnblogs.com/pixel-Teee/p/16612446.html