# IEntityReactor 接口

> 文档：`vjcad/doc/reactor/reactor-system.md`

`IEntityReactor` 是 vjcad 内置的"反应器实体"接口。任何实体（通常是 `CustomEntityBase` 派生类）实现该接口后，就能在文档加载、保存、Owner 变化时被反应器管理器（`Engine.reactorManager`）调度。

> ⚠️ 想搭建简单的运行时联动，请先看 [event-based](event-based.md)，那种方式更轻量。
> 仅在你需要 **跨会话保存关联关系** 或 **构建标注/约束类自定义实体** 时使用本接口。

## 1. 接口定义

```ts
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

```ts
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. 自定义反应器实体骨架

```ts
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](../entity/custom-entity.md)。

## 4. 反应器管理器

```ts
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](dimension-association.md) 的顺序一致）
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`
