唯杰地图 logo唯杰地图
VJMAP
EN

基于事件的反应器

vjcad/references/reactor/event-based.md 5.7 KB131 行
原文

适用:业务自定义联动、统计监控、临时约束、操作日志。 关键示例:

  • 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 — 全生命周期日志