基于事件的反应器
适用:业务自定义联动、统计监控、临时约束、操作日志。 关键示例:
vjcad/example/reactor/01basic.js— 圆 → 自动外接框vjcad/example/reactor/03multitype.js— 实体统计 / 同类型颜色同步vjcad/example/reactor/05lifecycle.js— 全生命周期日志
1. 最小骨架
事件型反应器的核心就是订阅 CadEvents.EntityAdded / EntityModified / EntitiesErased:
class CircleBoundingBoxReactor {
constructor() {
this.boundingLines = new Map(); // ownerId -> Lines[]
}
install() {
Engine.eventManager.on(CadEvents.EntityAdded, args => {
if (args.entity.type === 'CIRCLE') this.create(args.entity);
});
Engine.eventManager.on(CadEvents.EntityModified, args => {
if (args.entity.type === 'CIRCLE') this.update(args.entity);
});
Engine.eventManager.on(CadEvents.EntitiesErased, args => {
args.entities.forEach(e => e.type === 'CIRCLE' && this.remove(e));
});
}
create(c) { /* new LineEnt(...) -> Engine.addEntities */ }
update(c) { /* 修改已存在的 line.startPoint/endPoint */ }
remove(c) { /* Engine.eraseEntities(lines) */ }
}
new CircleBoundingBoxReactor().install();
完整代码:
vjcad/example/reactor/01basic.js
⚠️
EntityModified的派发时机:实体 setter /setModified()只置isDirty,不发事件;事件是在下一次Engine.regen()的 bucket 脏扫描里批量派发的(每个脏实体一次)。所以:① 编程式改完不regen就收不到;② 同一实体在两次 regen 之间改多次只收到一次;③Engine.eraseEntities是软删除(置isAlive=false并setModified()),刚删除的实体也会在下次 regen 收到一条EntityModified——回调开头先if (!args.entity.isAlive) return;。
2. 防止递归触发
EntityModified 事件里再去改实体会再次触发 EntityModified,必须做闭锁。例如几何约束反应器:
class EndpointConstraintReactor {
constructor() {
this.constraints = []; // {line1Id, point1, line2Id, point2}
this.updating = false;
}
install() {
Engine.eventManager.on(CadEvents.EntityModified, args => {
if (this.updating || !args.entity.isAlive) return;
this.updating = true;
try {
this.applyConstraints(args.entity);
} finally {
this.updating = false;
}
});
}
}
完整代码:
vjcad/example/reactor/04constraint.js
3. 多类型监控(统计 / 颜色同步)
订阅多个事件,按 entity.type 分类计数;颜色同步则用 Engine.getEntities().filter(...) 批量改:
Engine.eventManager.on(CadEvents.EntityAdded, ({ entity }) => {
if (entity.type in stats) {
stats[entity.type]++;
stats.total++;
updateStatsText();
}
});
Engine.eventManager.on(CadEvents.EntityModified, ({ entity }) => {
const prev = typeColors.get(entity.type);
if (prev !== undefined && entity.color !== prev) {
Engine.getEntities()
.filter(e => e.type === entity.type && e.color !== entity.color)
.forEach(e => { e.color = entity.color; });
Engine.regen();
}
typeColors.set(entity.type, entity.color);
});
完整代码:
vjcad/example/reactor/03multitype.js
4. 生命周期日志(追踪某个实体的所有变化)
Engine.eventManager.on(CadEvents.EntityAdded, a => log('创建', a.entity));
Engine.eventManager.on(CadEvents.EntityModified, a => log('修改', a.entity));
Engine.eventManager.on(CadEvents.EntitiesErased, a => a.entities.forEach(e => log('删除', e)));
Engine.eventManager.on(CadEvents.SelectionChanged, a => {
const n = a.selections?.length || 0;
if (n > 0) log(`选择 ${n} 个实体`);
});
完整代码:
vjcad/example/reactor/05lifecycle.js
5. 与 PLINE 等交互命令
PLINE / SPLINE 这类多点交互命令的事件时机比较特殊(首点不触发 EntityAdded、第二点起开始触发、命令结束才有最终实体),如果反应器要响应折线绘制完成,应当:
- 监听
CadEvents.CommandEnded+args.commandName === 'PLINE', - 然后取
Engine.getEntities(e => e.type === 'PLINE').slice(-1)[0]拿到最终实体。
详见 event/pline-timing.md。
红旗
- 事件派发不 await 回调(
emit同步调用 handler、忽略返回值):async回调不会阻塞命令循环,但await之后再设args.cancel = true已经无效(emit早已返回),回调返回的 rejected Promise 也不会被emit的 try/catch 捕获(变成 unhandledrejection)——可取消事件要同步判断、同步设cancel,异步回调自己 try/catch - 每次集中修改完一定要
Engine.regen() off必须传入与on时完全相同的 handler 引用,否则解绑失败- 文档关闭、视图重建后,事件监听不会自动清理;
DocumentClosing在当前版本从不派发,清理要放在自己的销毁流程里(如调cadView.destroy()之前、插件onDeactivate)手动Engine.eventManager.off(...)
参考(官方文档 MCP)
- 文档:
vjcad/doc/reactor/index.md、vjcad/doc/event/event-types.md - 示例:
vjcad/example/reactor/01basic.js— 圆 → 自动外接框vjcad/example/reactor/03multitype.js— 实体统计 / 同类型颜色同步vjcad/example/reactor/04constraint.js— 端点约束(防递归)vjcad/example/reactor/05lifecycle.js— 全生命周期日志