其他分享
首页 > 其他分享> > Game Engine MetaData Creation With Clang

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++允许新的类型发生,在一些地方。
image

还有一些没有覆盖,比如函数私有的类型,还有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

image

基础类型还有成员变量应当是清晰的,但是静态类成员可能怪异的,对于这个列表。

Data Gathering-External

...

标签:Engine,childType,Creation,Clang,cursor,Game,CXCursor,clang,类型
来源: https://www.cnblogs.com/pixel-Teee/p/16612446.html