【ZeloEngine】反射系统填坑小结
总结一下反射系统,下称ZHT
ZHT架构/数据流图
今天是入行两周年,本想写个入行小结,想想还是踏实点把坑填完,写个DevLog
本文接续之前一篇文章的原理描述,主要讲讲开发后的反思
另外说明,ZHT当然是下班时间开发的,所以进度会慢很多,好在能每天坚持写一点,代码也就攒起来了
【ZeloEngine】重写反射和对象系统_zoloypzuo的博客-CSDN博客
Demo
cyclone
生成代码位于__ZHT/
zoloypzuo/cyclone: 《游戏物理引擎开发》源码魔改
ogre
编译通过,ZHT运行5.5s
PropertySheet
PropertySheet,类似Unity的MonoBehavior,自动在Inspector上画界面编辑数据
特性列表
- 对标Swig,生成Lua脚本绑定
- 对标Moc,UBT的元信息标记机制meta.cs
- 对标C#,完全自动反射,生成RTTR绑定
- 生成imgui界面
- 丰富完整的C++信息收集,可以扩展后端,是通用的代码生成方案
语法特性
支持
- 全局函数
- 全局变量
- namespace
- struct/class // 统一为class
- enum
- 嵌套的class和enum
不支持
- C++模板 // 需要实例化
- C# property // 没必要
meta.cs不支持 // 用静态成员代替,因为C#语法不支持全局
- 全局函数
- 全局变量
特色:meta.cs
理解moc:是一种标记语言
moc的缺点
- moc标记,都是宏,缺乏IDE支持,重构,报错等
- moc标记,淹没在大量moc编译器不关心的代码中
- 不支持给第三方库打moc标记
meta.cs解决了以上问题
- 标记是C#语法,可以报错
- 标记与源码分离,容易统计分析和定位
- 支持第三方库
UBT vs ZHT
应用
- PropertySheet,配置表编辑器
- 替代Ver1的手工Lua脚本绑定,写脚本胶水从此无摩擦
- C++反射,可以走RTTR反射来调用了,更加动态的C++
- 对象模型中的其他子模块,GC,序列化,都依赖于反射系统
懒人模式
对标C#,最好不要写标记,正常写C++,然后就自带反射和导出Lua
这是目前努力的方向
这样做的好处
- 把ZHT隐藏起来,用户不需要操心怎么标记导出反射
- 便于单元测试,不需要费心维护测试用例
单元测试
由于程序的特点+懒人模式,可以很低成本地scale测试用例,来保证程序的健壮性
测试用例
- 重写Ver1的Lua驱动配置文件绑定,保证功能一致
- UHT,QtMoc,以及cocos的binding-generator,一些非真实的test-case,主要是覆盖C++语法
- 真实的项目:cyclone,ogre
如果能完整地反射一个ogre体量的引擎,基本ZHT就比较可用了
反思
- 基本思路清楚,快速搭建出第一个能跑的原型,只花了一周
- 实际填坑,实现规格(spec)以及自动化到铺量可用,花了一个月,细节很多
- 反复问自己几个问题
- 是什么?解决了什么问题?
- 重点难点是什么?
- 竞品方案对比
ZHT是什么?
- 解析C++代码结构到反射信息(中间表示),然后再生成多种目标代码
- 使用C#为C++标记上元信息,为ZHT和游戏/编辑器运行时提供元信息
重点和难点是什么?
- 理解源语言,也就是C++编译器
- 理解目标语言
- 代码生成是一种优化方法/自动化方法,要切实地自动化
对于反射系统而言,我们在乎声明,结构,类型,不在乎函数体里的计算代码
这块把重点放在了C++上,增进对C++编译器的理解对我目前的作用很大
竞品方案
- Lua脚本绑定
- 对标Swig,Swig的问题是黑盒不好控制,但是C++特性支持丰富
- cocos的binding-generator
- moc,UBT
奇怪的case清单
其实每个代码库都有自己的编程风格,ogre,ois,cyclone已经算是非常规范的OOP代码库了,至少保证内部风格统一
ZHT也主要是为我自己服务,所以不必去兼容所有库的写法,只是利用其他库作为测试用例来增强健壮性
不处理template
template<typename T, size_t Alignment>
struct AlignedAllocator : public std::allocator<T>
两个类互相依赖,ctor可以定义在类外面,问题是这样就需要去找这个类的引用,ZHT目前是直接从栈顶拿一个class,懒得搞搜索了,corner case
// these functions could not be defined within the class definition of class
// Radian because they required class Degree to be defined
inline Radian::Radian ( const Degree& d ) : mRad(d.valueRadians()) {}
指针不处理
std::vector<Plane>* planes;
const需要只读,目前先忽略const,建议封装成get函数
const OIS_ERROR eType;
const int eLine;
const MouseState& state;