1. 中望CAD二次开发入门:ZRX C++反应器基础
中望CAD作为国产CAD软件的领军产品,其二次开发能力一直备受行业关注。最近在做一个机械设计自动化项目时,我不得不深入研究ZRX C++中的反应器机制。这个看似基础的概念,在实际应用中却有着令人惊讶的深度和灵活性。
反应器(Reactor)机制是中望CAD二次开发的核心架构之一,它本质上是一种事件驱动编程模型。与传统的轮询方式不同,反应器允许开发者在特定事件发生时自动执行预设的回调函数。这种机制在CAD二次开发中尤为重要——当用户绘制图形、修改对象或执行命令时,我们需要实时响应这些操作并做出相应处理。
2. 反应器基础概念与工作原理
2.1 反应器类型与适用场景
中望CAD ZRX C++提供了多种反应器类型,每种都针对特定的应用场景:
-
数据库反应器(Database Reactor):监视图形数据库变化
- 典型应用:自动更新关联尺寸标注
- 触发事件:对象添加、修改、删除
-
编辑器反应器(Editor Reactor):跟踪用户交互行为
- 典型应用:自定义命令交互流程
- 触发事件:命令开始、结束、取消
-
对象反应器(Object Reactor):绑定到特定图形对象
- 典型应用:智能图块行为控制
- 触发事件:对象修改、删除、复制
-
命令反应器(Command Reactor):监控命令执行过程
- 典型应用:命令日志记录
- 触发事件:命令启动、完成、取消
2.2 反应器生命周期管理
反应器的生命周期管理是开发中最容易出问题的环节之一。一个典型的反应器使用流程包括:
- 创建反应器实例
- 注册到相应通知系统
- 实现回调函数
- 适时移除反应器
重要提示:忘记移除反应器是内存泄漏的常见原因。反应器应在其监视的对象生命周期结束时被移除。
3. 实战:创建一个数据库反应器
3.1 基础实现步骤
让我们通过一个具体案例来理解反应器的实现。假设我们需要在图形数据库发生变化时自动备份:
cpp复制class MyDbReactor : public ZcDbDatabaseReactor {
public:
virtual void objectAppended(const ZcDbDatabase* db, const ZcDbObject* obj) {
// 对象添加时的处理
acutPrintf(_T("对象已添加,类型:%s\n"), obj->isA()->name());
}
virtual void objectModified(const ZcDbDatabase* db, const ZcDbObject* obj) {
// 对象修改时的处理
acutPrintf(_T("对象已修改,句柄:%ld\n"), obj->objectId().handle());
}
virtual void objectErased(const ZcDbDatabase* db, const ZcDbObject* obj,
ZSoft::Boolean pErased) {
// 对象删除时的处理
acutPrintf(_T("对象%s被删除\n"), pErased ? _T("") : _T("恢复"));
}
};
// 注册反应器
void registerReactor() {
static MyDbReactor* pReactor = new MyDbReactor;
acdbHostApplicationServices()->workingDatabase()->addReactor(pReactor);
}
// 移除反应器
void removeReactor() {
ZcDbDatabase* pDb = acdbHostApplicationServices()->workingDatabase();
ZcDbDatabaseReactor* pReactor = pDb->firstReactor();
while(pReactor) {
if(pReactor->isKindOf(MyDbReactor::desc())) {
pDb->removeReactor(pReactor);
delete pReactor;
break;
}
pReactor = pReactor->next();
}
}
3.2 性能优化技巧
在实际项目中,反应器的性能影响不容忽视。以下是我总结的几个关键优化点:
- 回调函数精简:保持回调函数尽可能简短,避免复杂计算
- 批量操作处理:使用
beginDeepClone()/endDeepClone()等批量操作通知 - 反应器优先级:合理设置反应器执行顺序
- 条件过滤:在回调中尽早过滤不需要处理的事件
4. 反应器高级应用与陷阱规避
4.1 反应器链式调用问题
反应器的一个常见陷阱是链式调用(Reactor Chain)。例如:
- 反应器A响应对象修改事件
- 在回调中修改了另一个对象
- 触发反应器B的回调
- B又修改了其他对象...
这种链式调用可能导致不可预知的循环和性能问题。解决方案包括:
- 设置标志位防止递归
- 使用
disableReactor()临时禁用反应器 - 将修改操作放入队列延迟执行
4.2 多文档环境处理
在多文档界面(MDI)环境下,反应器管理需要特别注意:
cpp复制// 为每个文档创建独立的反应器
void createDocReactor(ZcDbDatabase* pDb) {
if(!pDb->hasReactorOfType(MyDbReactor::desc())) {
MyDbReactor* pReactor = new MyDbReactor;
pDb->addReactor(pReactor);
}
}
// 文档关闭时清理反应器
void docCloseReactor(ZcDbDatabase* pDb) {
ZcDbDatabaseReactor* pReactor = pDb->firstReactor();
while(pReactor) {
if(pReactor->isKindOf(MyDbReactor::desc())) {
pDb->removeReactor(pReactor);
delete pReactor;
break;
}
pReactor = pReactor->next();
}
}
5. 反应器调试与问题排查
5.1 常见问题速查表
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 反应器不触发 | 未正确注册 | 检查addReactor调用 |
| 程序崩溃 | 反应器未移除 | 确保析构时移除 |
| 性能下降 | 回调函数过重 | 优化回调逻辑 |
| 死循环 | 链式调用 | 添加递归保护 |
5.2 调试技巧
- 日志记录:在回调中添加详细的日志输出
- 断点设置:在Visual Studio中设置条件断点
- 反应器列表:通过遍历查看当前注册的反应器
- 内存检查:使用工具检查反应器内存泄漏
6. 反应器在实际项目中的应用案例
6.1 自动标注更新系统
在一个机械设计项目中,我们使用反应器实现了尺寸标注的自动更新:
- 创建数据库反应器监视关键几何图形
- 当图形位置变化时,查找关联标注
- 重新计算并更新标注值
- 添加事务处理确保数据一致性
关键实现代码片段:
cpp复制void DimensionUpdater::objectModified(const ZcDbDatabase* db, const ZcDbObject* obj) {
if(isGeometryObject(obj)) { // 检查是否是关键几何图形
ZcArray<ZcDbObjectId> dimIds = findRelatedDimensions(obj->objectId());
// 在事务中更新标注
ZcDbDatabase* pDb = const_cast<ZcDbDatabase*>(db);
ZcDbTransactionManager* pTM = pDb->transactionManager();
ZcDbTransaction* pTrans = pTM->startTransaction();
try {
for(int i=0; i<dimIds.length(); i++) {
ZcDbDimension* pDim = nullptr;
if(Zcad::eOk == zcdbOpenObject(pDim, dimIds[i], ZcDb::kForWrite)) {
updateDimension(pDim); // 更新标注值
pDim->close();
}
}
pTM->endTransaction();
} catch(...) {
pTM->abortTransaction();
throw;
}
}
}
6.2 智能元件库系统
另一个案例是智能元件库系统,使用反应器实现:
- 对象反应器监视元件实例
- 当元件被修改时检查参数有效性
- 自动同步元件库中的主定义
- 维护元件间的关联关系
7. 反应器开发的最佳实践
经过多个项目的实践,我总结了以下经验法则:
- 单一职责原则:每个反应器只关注一种类型的事件
- 短生命周期:不需要时及时移除反应器
- 异常安全:回调函数必须处理异常情况
- 线程安全:考虑多线程环境下的安全性
- 性能监控:定期检查反应器的性能影响
对于复杂项目,建议建立反应器管理系统:
cpp复制class ReactorManager {
public:
static void addReactor(ZcDbDatabaseReactor* pReactor, ZcDbDatabase* pDb) {
if(!pDb) pDb = acdbHostApplicationServices()->workingDatabase();
pDb->addReactor(pReactor);
m_reactors.append(pReactor);
}
static void removeAllReactors() {
for(int i=0; i<m_reactors.length(); i++) {
ZcDbDatabase* pDb = acdbHostApplicationServices()->workingDatabase();
pDb->removeReactor(m_reactors[i]);
delete m_reactors[i];
}
m_reactors.removeAll();
}
private:
static ZcArray<ZcDbDatabaseReactor*> m_reactors;
};
8. 反应器与其他ZRX技术的协同
反应器很少单独使用,通常与其他ZRX技术配合:
- 与事务结合:在反应器回调中使用事务确保数据一致性
- 与通知系统结合:通过反应器触发自定义通知
- 与扩展数据结合:使用反应器维护扩展数据(XData)
- 与自定义对象结合:为自定义对象添加专用反应器
一个典型的协同案例是自定义对象的持久化反应器:
cpp复制class MyEntity : public ZcDbEntity {
// ...其他成员函数...
virtual Zcad::ErrorStatus dwgInFields(ZcDbDwgFiler* pFiler) {
// 从DWG文件加载时
assertWriteEnabled();
Zcad::ErrorStatus es = ZcDbEntity::dwgInFields(pFiler);
if(es != Zcad::eOk) return es;
// 添加反应器
if(!m_pReactor) {
m_pReactor = new MyEntityReactor(this);
addReactor(m_pReactor);
}
return Zcad::eOk;
}
virtual Zcad::ErrorStatus dwgOutFields(ZcDbDwgFiler* pFiler) const {
// 保存到DWG文件时
assertReadEnabled();
return ZcDbEntity::dwgOutFields(pFiler);
}
private:
MyEntityReactor* m_pReactor = nullptr;
};
9. 反应器性能分析与优化
在大型CAD项目中,反应器的性能影响可能变得显著。以下是一些性能分析技巧:
- 时间测量:使用
ZcTime类测量回调执行时间 - 调用统计:记录各回调的触发频率
- 内存分析:检查反应器的内存占用
- 依赖分析:绘制反应器依赖关系图
一个简单的性能分析反应器实现:
cpp复制class ProfilingReactor : public ZcDbDatabaseReactor {
public:
virtual void objectModified(const ZcDbDatabase* db, const ZcDbObject* obj) {
ZcTime start = ZcTime::getCurrentTime();
// 实际处理逻辑...
ZcTime end = ZcTime::getCurrentTime();
m_totalTime += end - start;
m_callCount++;
if(m_callCount % 100 == 0) {
acutPrintf(_T("平均回调时间:%.3f ms\n"),
m_totalTime.milliseconds()/m_callCount);
}
}
private:
ZcTimeSpan m_totalTime;
int m_callCount = 0;
};
10. 反应器在特殊场景下的应用
10.1 批量导入处理
在批量导入外部数据时,常规的反应器处理可能导致性能问题。解决方案:
- 导入前禁用相关反应器
- 使用
beginNotify/endNotify包裹批量操作 - 导入后手动处理必要通知
cpp复制void importExternalData(const ZcString& filePath) {
ZcDbDatabase* pDb = acdbHostApplicationServices()->workingDatabase();
// 禁用反应器
pDb->disableReactorOfType(MyDbReactor::desc());
try {
pDb->beginNotify(); // 开始批量通知
// 执行导入操作...
importDataFromFile(filePath);
pDb->endNotify(); // 结束批量通知
} catch(...) {
pDb->endNotify();
pDb->enableReactorOfType(MyDbReactor::desc());
throw;
}
// 重新启用反应器
pDb->enableReactorOfType(MyDbReactor::desc());
// 手动触发更新
if(m_pUpdateReactor) {
m_pUpdateReactor->forceFullUpdate();
}
}
10.2 撤销/重做处理
处理撤销(Undo)和重做(Redo)时需要特别注意反应器的状态一致性:
- 在撤销记录中保存反应器状态
- 避免在撤销过程中触发不必要的事件
- 使用
ZcDbObject::erased()和ZcDbObject::goodOverride()正确处理对象状态变化
11. 反应器单元测试策略
为反应器代码编写有效的单元测试颇具挑战性。我采用的策略包括:
- 模拟事件触发:创建专用测试类模拟各种CAD事件
- 状态快照比较:在执行操作前后比较数据库状态
- 回调覆盖率:确保测试覆盖所有回调分支
- 性能基准:建立反应器性能基准测试
一个简单的测试框架示例:
cpp复制class ReactorTestFixture {
public:
ReactorTestFixture() {
m_pDb = new ZcDbDatabase;
m_pReactor = new TestReactor;
m_pDb->addReactor(m_pReactor);
}
~ReactorTestFixture() {
m_pDb->removeReactor(m_pReactor);
delete m_pReactor;
delete m_pDb;
}
void triggerObjectAdd() {
ZcDbObject* pObj = new ZcDbCircle;
ZcDbBlockTableRecord* pBTR = getModelSpace(m_pDb);
ZcDbTransaction* pTrans = m_pDb->transactionManager()->startTransaction();
pBTR->appendAcDbEntity(pObj);
m_pDb->transactionManager()->endTransaction();
pObj->close();
pBTR->close();
}
int getCallbackCount() const { return m_pReactor->getCallbackCount(); }
private:
ZcDbDatabase* m_pDb;
TestReactor* m_pReactor;
};
TEST_F(ReactorTestFixture, ObjectAddTest) {
EXPECT_EQ(0, getCallbackCount());
triggerObjectAdd();
EXPECT_EQ(1, getCallbackCount());
}
12. 反应器资源管理与内存安全
反应器的资源管理需要特别注意以下几点:
- 所有权明确:确定谁拥有反应器对象的生命周期
- 引用计数:对于共享反应器考虑使用智能指针
- 析构顺序:确保反应器在数据库之前被移除
- 异常安全:所有回调都应考虑异常情况
一个使用智能指针管理反应器的示例:
cpp复制class SafeReactorWrapper {
public:
explicit SafeReactorWrapper(ZcDbDatabase* pDb) : m_pDb(pDb) {
m_pReactor = std::make_shared<MyDbReactor>();
m_pDb->addReactor(m_pReactor.get());
}
~SafeReactorWrapper() {
if(m_pDb && m_pReactor) {
m_pDb->removeReactor(m_pReactor.get());
}
}
// 禁用拷贝和赋值
SafeReactorWrapper(const SafeReactorWrapper&) = delete;
SafeReactorWrapper& operator=(const SafeReactorWrapper&) = delete;
private:
ZcDbDatabase* m_pDb;
std::shared_ptr<ZcDbDatabaseReactor> m_pReactor;
};
13. 反应器与多线程编程
虽然中望CAD本身是单线程应用,但在某些场景下仍需要考虑线程安全:
- 异步处理:将反应器触发的工作放入后台线程
- 线程间通信:使用安全队列传递反应器事件
- 资源锁:保护共享资源访问
- 反应器代理:在主线程执行实际回调
一个线程安全的反应器事件处理器实现:
cpp复制class ThreadSafeReactor : public ZcDbDatabaseReactor {
public:
ThreadSafeReactor() {
m_workerThread = std::thread(&ThreadSafeReactor::processEvents, this);
}
~ThreadSafeReactor() {
m_stopFlag = true;
m_eventCond.notify_all();
if(m_workerThread.joinable()) {
m_workerThread.join();
}
}
virtual void objectModified(const ZcDbDatabase* db, const ZcDbObject* obj) override {
std::lock_guard<std::mutex> lock(m_eventMutex);
m_eventQueue.emplace(db, obj->objectId());
m_eventCond.notify_one();
}
private:
void processEvents() {
while(!m_stopFlag) {
std::unique_lock<std::mutex> lock(m_eventMutex);
m_eventCond.wait(lock, [this]{
return !m_eventQueue.empty() || m_stopFlag;
});
if(m_stopFlag) break;
auto event = m_eventQueue.front();
m_eventQueue.pop();
lock.unlock();
// 实际处理事件
handleEvent(event.db, event.objId);
}
}
void handleEvent(const ZcDbDatabase* db, ZcDbObjectId objId) {
// 实际的事件处理逻辑
}
struct Event {
const ZcDbDatabase* db;
ZcDbObjectId objId;
};
std::thread m_workerThread;
std::mutex m_eventMutex;
std::condition_variable m_eventCond;
std::queue<Event> m_eventQueue;
std::atomic<bool> m_stopFlag{false};
};
14. 反应器在插件系统中的应用
在开发中望CAD插件时,反应器可以发挥重要作用:
- 插件初始化:使用反应器检测插件加载时机
- 功能激活:通过反应器动态启用/禁用插件功能
- 资源清理:在插件卸载时自动清理反应器
- 跨插件通信:通过反应器实现插件间消息传递
一个插件系统的反应器管理示例:
cpp复制class PluginManagerReactor : public ZcRxModuleReactor {
public:
virtual void moduleLoaded(const ZcString& moduleName) {
if(moduleName == "MyPlugin") {
// 初始化插件相关反应器
initPluginReactors();
}
}
virtual void moduleUnloaded(const ZcString& moduleName) {
if(moduleName == "MyPlugin") {
// 清理插件相关反应器
cleanupPluginReactors();
}
}
private:
void initPluginReactors() {
// 初始化各种反应器
}
void cleanupPluginReactors() {
// 清理反应器
}
};
// 注册模块反应器
void initPluginManager() {
static PluginManagerReactor* pReactor = new PluginManagerReactor;
zcrxRegisterModuleReactor(pReactor);
}
15. 反应器与自定义命令集成
将反应器与自定义命令结合可以创建更智能的交互体验:
- 命令反应器:跟踪命令执行状态
- 临时反应器:仅在命令执行期间存在
- 交互式反应器:响应用户输入事件
- 命令链:通过反应器串联多个命令
一个交互式绘图命令的实现示例:
cpp复制class DrawCommandReactor : public ZcEdCommandReactor {
public:
virtual void commandWillStart(const ZcString& cmdStr) {
if(cmdStr == "MYDRAW") {
// 初始化绘图反应器
m_pDrawReactor = new DrawReactor;
acedEditor->addReactor(m_pDrawReactor);
}
}
virtual void commandEnded(const ZcString& cmdStr) {
if(cmdStr == "MYDRAW") {
// 移除绘图反应器
acedEditor->removeReactor(m_pDrawReactor);
delete m_pDrawReactor;
m_pDrawReactor = nullptr;
}
}
private:
DrawReactor* m_pDrawReactor = nullptr;
};
// 注册命令反应器
void initCommandReactor() {
static DrawCommandReactor* pCmdReactor = new DrawCommandReactor;
acedRegisterCommandReactor(pCmdReactor);
}
16. 反应器与用户界面交互
反应器可以增强CAD用户界面的交互性:
- 选择集反应器:响应对象选择变化
- 界面反应器:监视工具栏、面板状态
- 实时预览:通过反应器实现动态预览
- 上下文菜单:根据当前状态动态调整菜单
一个选择集变化反应的实现:
cpp复制class SelectionReactor : public ZcEdSelectionSetReactor {
public:
virtual void selectionSetChanged(const ZcEdSelectionSet* pSSet) {
int count = 0;
pSSet->numEntities(count);
if(count == 1) {
ZcDbObjectId objId;
pSSet->objectIdAt(0, objId);
ZcDbObject* pObj = nullptr;
if(zcdbOpenObject(pObj, objId, ZcDb::kForRead) == Zcad::eOk) {
displayObjectInfo(pObj);
pObj->close();
}
} else {
clearObjectInfo();
}
}
private:
void displayObjectInfo(ZcDbObject* pObj) {
// 显示对象信息
}
void clearObjectInfo() {
// 清除信息显示
}
};
// 注册选择集反应器
void initSelectionReactor() {
static SelectionReactor* pSelReactor = new SelectionReactor;
acedEditor->selectionSetManager()->addReactor(pSelReactor);
}
17. 反应器与CAD系统事件
除了数据库事件,反应器还可以响应各种系统级事件:
- 文件操作:DWG打开、保存、关闭
- 系统配置:变量改变、配置更新
- 显示更新:视图变化、重生成
- 应用程序:CAD启动、退出
一个监视系统变量变化的反应器示例:
cpp复制class SysVarReactor : public ZcEdSysVarReactor {
public:
virtual void sysVarChanged(const ZcString& varName) {
if(varName == "FILLMODE") {
bool fillMode = acedGetVarInt("FILLMODE", 0) != 0;
updateAllHatches(fillMode);
}
}
private:
void updateAllHatches(bool fillMode) {
// 更新所有填充图案状态
}
};
// 注册系统变量反应器
void initSysVarReactor() {
static SysVarReactor* pVarReactor = new SysVarReactor;
acedEditor->sysVarManager()->addReactor(pVarReactor);
}
18. 反应器性能监控与调优
对于长期运行的CAD应用,反应器性能监控至关重要:
- 回调统计:记录各回调的执行频率和耗时
- 依赖分析:分析反应器间的调用关系
- 瓶颈定位:识别性能热点
- 动态调整:根据负载调整反应器行为
一个简单的性能监控实现:
cpp复制class ProfilingReactor : public ZcDbDatabaseReactor {
public:
struct CallbackStats {
int callCount = 0;
double totalTime = 0;
};
virtual void objectModified(const ZcDbDatabase* db, const ZcDbObject* obj) {
auto start = std::chrono::high_resolution_clock::now();
// 实际处理逻辑...
auto end = std::chrono::high_resolution_clock::now();
std::chrono::duration<double> elapsed = end - start;
std::lock_guard<std::mutex> lock(m_statsMutex);
m_stats["objectModified"].callCount++;
m_stats["objectModified"].totalTime += elapsed.count();
}
void printStats() const {
std::lock_guard<std::mutex> lock(m_statsMutex);
for(const auto& [name, stats] : m_stats) {
acutPrintf(_T("%s: 调用次数=%d, 总耗时=%.3fs, 平均=%.3fms\n"),
name.c_str(), stats.callCount, stats.totalTime,
stats.totalTime*1000/stats.callCount);
}
}
private:
mutable std::mutex m_statsMutex;
std::unordered_map<std::string, CallbackStats> m_stats;
};
19. 反应器在大型项目中的架构设计
在大型CAD二次开发项目中,反应器的架构设计需要考虑:
- 分层设计:基础反应器、业务反应器、应用反应器
- 消息总线:通过反应器实现事件总线
- 模块化:每个模块管理自己的反应器
- 配置化:动态加载/卸载反应器配置
一个模块化反应器架构示例:
cpp复制class ReactorModule {
public:
virtual ~ReactorModule() {
unregisterReactors();
}
void registerReactors() {
if(!m_bRegistered) {
doRegisterReactors();
m_bRegistered = true;
}
}
void unregisterReactors() {
if(m_bRegistered) {
doUnregisterReactors();
m_bRegistered = false;
}
}
protected:
virtual void doRegisterReactors() = 0;
virtual void doUnregisterReactors() = 0;
private:
bool m_bRegistered = false;
};
class DimensionModule : public ReactorModule {
protected:
virtual void doRegisterReactors() override {
m_pDimReactor = new DimensionReactor;
acdbHostApplicationServices()->workingDatabase()->addReactor(m_pDimReactor);
}
virtual void doUnregisterReactors() override {
if(m_pDimReactor) {
ZcDbDatabase* pDb = acdbHostApplicationServices()->workingDatabase();
pDb->removeReactor(m_pDimReactor);
delete m_pDimReactor;
m_pDimReactor = nullptr;
}
}
private:
DimensionReactor* m_pDimReactor = nullptr;
};
20. 反应器开发的高级技巧与模式
20.1 反应器代理模式
对于需要动态启用/禁用的反应器功能,可以使用代理模式:
cpp复制class ReactorProxy : public ZcDbDatabaseReactor {
public:
void setTarget(ZcDbDatabaseReactor* pTarget, bool own = true) {
if(m_pOwnedTarget) {
delete m_pOwnedTarget;
}
m_pTarget = pTarget;
m_pOwnedTarget = own ? pTarget : nullptr;
}
virtual void objectModified(const ZcDbDatabase* db, const ZcDbObject* obj) override {
if(m_pTarget && m_bEnabled) {
m_pTarget->objectModified(db, obj);
}
}
void setEnabled(bool enabled) { m_bEnabled = enabled; }
bool isEnabled() const { return m_bEnabled; }
private:
ZcDbDatabaseReactor* m_pTarget = nullptr;
ZcDbDatabaseReactor* m_pOwnedTarget = nullptr;
bool m_bEnabled = true;
};
20.2 反应器组合模式
将多个反应器组合成一个复合反应器:
cpp复制class CompositeReactor : public ZcDbDatabaseReactor {
public:
void addReactor(ZcDbDatabaseReactor* pReactor) {
m_reactors.append(pReactor);
}
void removeReactor(ZcDbDatabaseReactor* pReactor) {
m_reactors.remove(pReactor);
}
virtual void objectModified(const ZcDbDatabase* db, const ZcDbObject* obj) override {
for(int i=0; i<m_reactors.length(); i++) {
m_reactors[i]->objectModified(db, obj);
}
}
private:
ZcArray<ZcDbDatabaseReactor*> m_reactors;
};
20.3 反应器与事务的深度集成
对于需要事务支持的高级场景,可以将反应器与事务深度集成:
cpp复制class TransactionalReactor : public ZcDbDatabaseReactor,
public ZcDbTransactionReactor {
public:
virtual void objectModified(const ZcDbDatabase* db, const ZcDbObject* obj) override {
if(!m_currentTrans) {
// 非事务性修改
handleStandaloneChange(db, obj);
} else {
// 事务性修改
m_transChanges[m_currentTrans].append({db, obj->objectId()});
}
}
virtual void transactionAboutToStart(ZcDbTransaction* pTrans) override {
m_currentTrans = pTrans;
m_transChanges[pTrans] = ZcArray<ChangeRecord>();
}
virtual void transactionEnded(ZcDbTransaction* pTrans, bool committed) override {
if(committed) {
const auto& changes = m_transChanges[pTrans];
for(const auto& change : changes) {
handleTransactionalChange(change.db, change.objId);
}
}
m_transChanges.erase(pTrans);
if(m_currentTrans == pTrans) {
m_currentTrans = nullptr;
}
}
private:
struct ChangeRecord {
const ZcDbDatabase* db;
ZcDbObjectId objId;
};
ZcDbTransaction* m_currentTrans = nullptr;
std::map<ZcDbTransaction*, ZcArray<ChangeRecord>> m_transChanges;
};