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