为了有效实现数据的同步,避免冗余的计算开销,PeriDyno引入时间戳的概念。通过维护一个全局变量的计数器来记录数据修改的时间,具体实现如下:
class TimeStamp
{
public:
TimeStamp() {};
~TimeStamp();
void mark();
bool operator > (TimeStamp& ts) { return (mTickTime > ts.mTickTime); }
bool operator < (TimeStamp& ts) { return (mTickTime < ts.mTickTime); }
private:
uint64 mTickTime = 0;
};
此外,每个Field维护两个时间戳并分别用tick()和tack()接口函数进行标记:
void tick();
void tack();
其中tick()函数表示数据更新的时间,tack()函数表示数据使用的时间。
以下图为例,当Module 1更新完之后,依次调用所有输出数据的tick()接口记录Field 8的更新时间。Module 2执行过程首先进行Field 9的tack()时间与Field 8的tick()时间进行比较,只有当Field 9的tack()时间早于Field 8的tick()才执行Module 2内部算法,完成Module 2状态标量的更新。当Module 2完成更新之后首先调用所有输入Field的tack()函数标记使用时间,然后再调用所有输出Field的tick()函数标记数据修改时间,从而完成模块更新。
当检测到Field 9的tack()时间不早于Field 8的tick()时间时,Module 2不需要更新因此会跳过执行。
多线程运行环境下,仿真管线和渲染管线可能存在读写冲突,PeriDyno通过互斥锁来实现管线之间的数据同步。
如上图所示,渲染管线与仿真管线分别运行独立线程。当渲染需要更新数据时,需要尝试锁住仿真管线。只有当仿真管线被锁之后,渲染管线才能调用SceneGraph()的updateGraphicsContext()函数更新渲染数据。更新完了之后释放互斥锁,仿真管线恢复运行。
上述同步机制的设计主要为了充分保证渲染和交互的流畅性,防止进程因为仿真计算任务过大而出现卡死的情况。