唯杰地图 logo唯杰地图
VJMAP
EN

IEntityReactor 接口

vjcad/references/reactor/ireactor-interface.md 7.4 KB156 行
原文

文档:vjcad/doc/reactor/reactor-system.md

IEntityReactor 是 vjcad 内置的"反应器实体"接口。任何实体(通常是 CustomEntityBase 派生类)实现该接口后,就能在文档加载、保存、Owner 变化时被反应器管理器(Engine.reactorManager)调度。

⚠️ 想搭建简单的运行时联动,请先看 event-based,那种方式更轻量。 仅在你需要 跨会话保存关联关系 或 构建标注/约束类自定义实体 时使用本接口。

1. 接口定义

interface IEntityReactor {
    readonly id: number;                           // 反应器实体 ID(数字,即 EntityBase.id)
    getOwnerIds(): number[];                       // 关联的 Owner id 列表
    onOwnerChanged(args: ReactorEventArgs): void;  // Owner 变化回调
    setReactorDirty(): void;                       // 标记需要更新
    isReactorDirty(): boolean;
    updateFromOwners(): boolean;                   // 从 Owner 重建自身几何
    unlinkAllOwners(): void;                       // 断开所有关联
    tryRegisterReactor(docId: number): boolean;    // 注册到文档反应器表
}

2. ReactorEvent 与 OwnerReference

enum ReactorEvent {
    Modified = 'modified',
    Erased   = 'erased',
    Copied   = 'copied',
    Unlinked = 'unlinked',
}

interface ReactorEventArgs {
    event: ReactorEvent;
    docId: number;
    ownerId: number;
    ownerEntity?: EntityBase;
    timestamp: number;
}

interface OwnerReference {
    entityId: number;
    segmentIndex?: number;                              // 多段线段索引
    pointType?: 'start' | 'end' | 'center' | 'midpoint'; // 端点类型
    parameterT?: number;                                // 0~1 曲线参数
    meta?: Record<string, any>;
}

OwnerReference 是把"关联到实体的哪个点"语义化保存下来的关键结构(端点、中点、参数 t、线段下标等),保证 Owner 变化时能精确重新定位。

3. 自定义反应器实体骨架

class CircleCenterMark extends CustomEntityBase implements IEntityReactor {
    readonly customType = 'CIRCLE_CENTER_MARK';
    readonly customDisplayName = '圆心标记';

    private _ownerId: number | null = null;
    private _ownerRef: OwnerReference | null = null;
    private _isDirty = false;
    private _center: Point2D = new Point2D(0, 0);

    getOwnerIds()  { return this._ownerId !== null ? [this._ownerId] : []; }
    isReactorDirty() { return this._isDirty; }
    setReactorDirty() { this._isDirty = true; }

    onOwnerChanged(args: ReactorEventArgs) {
        if (args.event === ReactorEvent.Modified) this.setReactorDirty();
        if (args.event === ReactorEvent.Erased)   this.unlinkAllOwners();
    }

    updateFromOwners() {
        if (!this._isDirty || this._ownerId === null) return false;
        // CadDocument 只有 findByObjectId(string);数字 id 走遍历查找
        const owner = Engine.getEntities(e => e.id === this._ownerId)[0];
        if (owner && owner.type === 'CIRCLE') {
            this._center = (owner as CircleEnt).center.clone();
            this.setModified();
        }
        this._isDirty = false;
        return true;
    }

    unlinkAllOwners() { this._ownerId = null; this._ownerRef = null; }

    tryRegisterReactor(docId: number) {
        if (this._ownerId !== null) {
            Engine.reactorManager.registerReactor(this, docId);
            return true;
        }
        return false;
    }

    linkToCircle(circle: CircleEnt) {
        this._ownerId = circle.id;
        this._ownerRef = { entityId: circle.id, pointType: 'center' };
        this._center = circle.center.clone();
        this.tryRegisterReactor(Engine.currentDoc.docId);
        this.setModified();
    }

    // 还需要实现 CustomEntityBase 的全部抽象成员:
    // getSnapPoints / getGripPoints / buildNestEnts / clone / fromDb / getEntityData / setEntityData
}

// 注册类型(key 须与 customType 一致),否则从 DWG/数据库加载时无法按 customType 还原实体
const CENTER_MARK_TYPE = 'CIRCLE_CENTER_MARK';
CustomEntityRegistry.getInstance().register(CENTER_MARK_TYPE, CircleCenterMark);

CustomEntityBase 的抽象成员共 9 个:customType、customDisplayName(只读属性)+ getSnapPoints()、getGripPoints()、buildNestEnts()(protected)、clone()、fromDb(dbData)、getEntityData()、setEntityData(data)。完整骨架见 vjcad/doc/reactor/reactor-system.md 与 entity/custom-entity。

4. 反应器管理器

const reactorMgr = Engine.reactorManager;

reactorMgr.registerReactor(reactorEntity, docId);   // 注册:参数顺序是 (reactor, docId?)
reactorMgr.unregisterReactor(reactorEntity.id, docId); // 注销:(reactorId, docId?)

// Owner 变化通知(系统在内置事件里会自动调用)
reactorMgr.notifyOwnerChanged({
    event: ReactorEvent.Modified,
    docId, ownerId: circle.id,
    ownerEntity: circle, timestamp: Date.now(),
});

// 批量刷新所有标记 dirty 的反应器(无参;方法名是 updateDirtyReactors)
reactorMgr.updateDirtyReactors();

5. 调用顺序

  1. 实体创建:new MyReactorEnt(...) → setDefaults() → Engine.addEntities(...)(Owner 与反应器实体都先入库) → linkToOwner(...)
  2. linkToOwner 内部要 tryRegisterReactor(docId)——管理器以 (docId, reactor.id) 为键登记,而 id 是 addEntities 时才分配的(之前为 0),addEntities 也不会自动补注册;所以必须先 add 再 link(与 dimension-association 的顺序一致)
  3. Owner 修改时由系统调用 onOwnerChanged → setReactorDirty()
  4. 框架在合适时机批量 updateDirtyReactors → 触发各 reactor 的 updateFromOwners()
  5. updateFromOwners 内部不要直接 Engine.regen(),让 setModified() 自动触发就好

红旗

  • getOwnerIds 必须返回稳定结果,不要每次 new 数组里塞 Math.random() 之类
  • clone() 时不要复制 ownerId(否则两个实体共享一个 Owner 的事件,更新会冲突),见示例骨架
  • DWG 持久化关心 getEntityData / setEntityData,所有 owner 信息都要在这里序列化
  • 不要在 onOwnerChanged 里立即重算几何(性能会爆炸),仅 setReactorDirty(),等批量刷新
  • reactorManager.registerReactor 无类型防护:传入不实现 IEntityReactor 的对象直接抛 xxx.getOwnerIds is not a function——不做接口校验,调用方须确保对象实现完整接口
  • 通知来源:ReactorEventBridge 订阅 EntityModified/EntityAdded/EntityErased——但 EntityErased 单数事件从不派发(erase 只发 EntitiesErased 复数),桥的这条订阅是死代码;EntityModified 也要等 Engine.regen() 的 bucket 扫描才派发(setModified() 只置旗标)。notifyErased 唯一触发点是 ERASE 撤销记录的 pushRedo(即 undo→redo 才通知删除)。编程式改动想立即联动:ReactorNotifier.notifyModified(entity) / notifyErased(entities) 手动驱动
  • suspend() 期间事件进 pendingEvents 队列(getStats().pendingEventCount 可查),resume() 时合并派发——可用于批量操作降噪

参考(官方文档 MCP)

  • 文档:vjcad/doc/reactor/reactor-system.md