You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
610 lines
20 KiB
610 lines
20 KiB
import * as THREE from 'three'
|
|
import type Viewport from '@/core/engine/Viewport'
|
|
import type BaseRenderer from '@/core/base/BaseRenderer'
|
|
import { getRenderer } from './ModuleManager'
|
|
import { getClosestObject, getLineId, parseLineId } from '@/core/ModelUtils'
|
|
import { Vector2 } from 'three'
|
|
|
|
/**
|
|
* 实体管理器
|
|
* 缓存所有 数据(ItemJson)和他们的关系, 以及渲染对象 THREE.Object3D
|
|
* 数据更新的流程是:
|
|
* 1. 状态管理器 StateManager 发出数据被修改的请求, 这些请求通常只有 ItemJson 数据
|
|
* 2. 实体管理器 (EntityManager) 需要计算所有相关的点和线的变化. 在 endUpdate 时需要根据 center / in / out 关系, 计算出: 点的创建 / 点的更新 / 点的删除 / 线的创建 / 线的更新 / 线的删除
|
|
* 3. 通知对应的渲染器 (Renderer) 进行点和线的创建 / 更新 / 删除
|
|
*/
|
|
export default class EntityManager {
|
|
viewport: Viewport
|
|
|
|
// 所有数据点的实体
|
|
private readonly entities = new Map<string, ItemJson>()
|
|
|
|
// 关系索引
|
|
private readonly relationIndex = new Map<string, Relation>()
|
|
|
|
// 所有 THREEJS "点"对象, 检索值是"点实体"的 id, 值是 THREE.Object3D 数组
|
|
private readonly objects = new Map<string, THREE.Object3D[]>()
|
|
|
|
// 所有 THREEJS "可选中"对象, 检索值是"点实体"的 id, 值是 THREE.Object3D 数组
|
|
private readonly _selectableObjects: THREE.Object3D[] = []
|
|
|
|
// 所有 THREEJS "线"对象, 检索值是"线实体"的 id, 取值方式是 {type}${startId}${endId}, 值是 THREE.Object3D 数组
|
|
private readonly lines = new Map<string, THREE.Object3D[]>()
|
|
|
|
// 差量渲染器
|
|
private readonly diffRenderer = new Map<string, BaseRenderer>()
|
|
// 线差量记录
|
|
private readonly lineDiffs = {
|
|
create: new Map<string, LineDiffItem>(),
|
|
update: new Map<string, LineDiffItem>(),
|
|
delete: new Map<string, LineDiffItem>()
|
|
}
|
|
// 记录所有发生过变化的实体, 用于在 endEntityUpdate 时进行写回给 StateManager
|
|
private readonly writeBackEntities = new Set<string>()
|
|
isUpdating = false
|
|
|
|
dispose() {
|
|
// 清理所有差量渲染器
|
|
for (const renderer of this.diffRenderer.values()) {
|
|
renderer.dispose()
|
|
}
|
|
this.diffRenderer.clear()
|
|
|
|
// 清理所有实体和关系索引
|
|
this.entities.clear()
|
|
this.relationIndex.clear()
|
|
this.objects.clear()
|
|
this.lines.clear()
|
|
this.writeBackEntities.clear()
|
|
}
|
|
|
|
/**
|
|
* 克隆需要回写的实体集合
|
|
*/
|
|
cloneWriteBackEntities(): Map<string, ItemJson> {
|
|
const entities = new Map<string, ItemJson>()
|
|
for (const id of this.writeBackEntities) {
|
|
const entity = this.entities.get(id)
|
|
if (!entity) continue
|
|
entities.set(id, _.cloneDeep(entity))
|
|
}
|
|
console.log('需要回写', entities.size, '行数据')
|
|
return entities
|
|
}
|
|
|
|
init(viewport: Viewport) {
|
|
this.viewport = viewport
|
|
}
|
|
|
|
/**
|
|
* 批量更新开始
|
|
*/
|
|
beginEntityUpdate(): void {
|
|
this.isUpdating = true
|
|
this.viewport.beginViewUpdate()
|
|
this.writeBackEntities.clear()
|
|
this.diffRenderer.clear()
|
|
this.lineDiffs.create.clear()
|
|
this.lineDiffs.update.clear()
|
|
this.lineDiffs.delete.clear()
|
|
}
|
|
|
|
/**
|
|
* 创建或更新一个实体, 这个点的 center[] / in[] / out[] 关联的点, 可能都要对应进行关联
|
|
*/
|
|
createOrUpdateEntity(entityRaw: ItemJson, option: EntityCudOption = {}): void {
|
|
if (!entityRaw?.id) {
|
|
throw new Error('Entity must have an id')
|
|
}
|
|
const entity = _.cloneDeep(entityRaw) as ItemJson
|
|
const originEntity = this.entities.get(entity.id)
|
|
|
|
// 找到这个数据的渲染器
|
|
const renderer = this.getDiffRenderer(entity.t)
|
|
|
|
// 先判断坐标是否变化
|
|
const coordinateChanged = originEntity?.tf && entity?.tf && !_.isEqual(originEntity.tf[0], entity.tf[0])
|
|
this.entities.set(entity.id, entity)
|
|
|
|
// 更新关系网
|
|
this.updateRelations(entity, originEntity)
|
|
|
|
if (coordinateChanged) {
|
|
this.writeBackEntities.add(entity.id)
|
|
// 点的坐标发生变化, 要通知所有关联线更新
|
|
const relation = this.relationIndex.get(entity.id)
|
|
|
|
if (relation) {
|
|
for (const type of (['center', 'in', 'out'] as LinkType[])) {
|
|
const relatedIds = relation[type]
|
|
if (!relatedIds) continue
|
|
|
|
for (const relatedId of relatedIds) {
|
|
const lineId = getLineId(entity.id, relatedId, type)
|
|
console.log(`[update] ${entity.id} -> ${relatedId} [${type}] => ${lineId}`)
|
|
this.lineDiffs.update.set(lineId, { startId: entity.id, endId: relatedId, type })
|
|
|
|
this.writeBackEntities.add(relatedId)
|
|
|
|
// 如果是双向线(比如 center),也要反向加一次
|
|
if (type === 'center') {
|
|
this.lineDiffs.update.set(lineId, { startId: relatedId, endId: entity.id, type })
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (typeof originEntity === 'undefined') {
|
|
renderer.createPoint(entity, option as RendererCudOption)
|
|
|
|
} else {
|
|
option.originEntity = _.cloneDeep(originEntity)
|
|
renderer.updatePoint(entity, option as RendererCudOption)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 删除实体, 与这个点有关的所有线都要删除, 与这个点有关系的点,都要对应的 center[] / in[] / out[] 都要断绝关系
|
|
*/
|
|
deleteEntity(id: string, option: EntityCudOption = {}): void {
|
|
const entity = this.entities.get(id)
|
|
if (!entity) return
|
|
|
|
option.originEntity = _.cloneDeep(entity)
|
|
this.writeBackEntities.add(id)
|
|
|
|
// 先生成线差量,再清理关系
|
|
this.generateLineDiffsForDelete(id)
|
|
this.removeRelations(id) // 清理关系
|
|
this.entities.delete(id) // 删除实体
|
|
|
|
this.getDiffRenderer(entity.t).deletePoint(id, option as RendererCudOption)
|
|
}
|
|
|
|
generateLineDiffsForDelete(id: string): void {
|
|
const relations = this.relationIndex.get(id)
|
|
if (!relations) return
|
|
|
|
const removeLine = (relatedId: string, type: LinkType) => {
|
|
const lineId = getLineId(id, relatedId, type)
|
|
this.writeBackEntities.add(relatedId)
|
|
this.lineDiffs.delete.set(lineId, { startId: id, endId: relatedId, type })
|
|
}
|
|
|
|
relations.center.forEach(relatedId => removeLine(relatedId, 'center'))
|
|
relations.input.forEach(relatedId => removeLine(relatedId, 'in'))
|
|
relations.output.forEach(relatedId => removeLine(relatedId, 'out'))
|
|
}
|
|
|
|
/**
|
|
* 批量更新结束, 结束后会触发视窗的渲染
|
|
* 这个方法最重要的是进行连线逻辑的处理
|
|
* - 如果进行了添加, 那么这个点的 center[] / in[] / out[] 关联的点, 都要对应进行关联
|
|
* - 如果进行了删除, 与这个点有关的所有线都要删除, 与这个点有关系的点,都要对应的 center[] / in[] / out[] 都要断绝关系
|
|
* - 如果进行了更新, 如果改了颜色/位置, 则需要在UI上进行对应修改,如果改了关系,需要与关联的节点批量调整
|
|
* 将影响到的所有数据, 都变成一个修改集合, 统一调用对应单元类型渲染器(BaseRenderer)的 createPoint / deletePoint / updatePoint / createLine / updateLine / deleteLine 方法
|
|
*/
|
|
endEntityUpdate(): void {
|
|
for (const [itemTypeName, renderer] of this.diffRenderer.entries()) {
|
|
// "线"创建
|
|
for (const [lineId, lineDiffItem] of this.lineDiffs.create.entries()) {
|
|
const start = this.entities.get(lineDiffItem.startId)
|
|
const end = this.entities.get(lineDiffItem.endId)
|
|
|
|
// 添加存在性检查
|
|
if (!start || !end) {
|
|
system.showErrorDialog(`无法创建线 ${lineId}, 起点或终点不存在`)
|
|
continue
|
|
}
|
|
|
|
if (start.t !== itemTypeName) {
|
|
// 只通知起点对应的渲染器
|
|
continue
|
|
}
|
|
renderer.createLine(start, end, lineDiffItem.type)
|
|
}
|
|
|
|
// "线"更新
|
|
for (const [lineId, lineDiffItem] of this.lineDiffs.update.entries()) {
|
|
const start = this.entities.get(lineDiffItem.startId)
|
|
const end = this.entities.get(lineDiffItem.endId)
|
|
|
|
// 添加存在性检查
|
|
if (!start || !end) {
|
|
system.showErrorDialog(`无法更新线 ${lineId}, 起点或终点不存在`)
|
|
continue
|
|
}
|
|
|
|
if (start.t !== itemTypeName) {
|
|
// 只通知起点对应的渲染器
|
|
console.error(`Line ${lineId} start point type mismatch: expected ${itemTypeName}, got ${start.t}`)
|
|
continue
|
|
}
|
|
renderer.updateLine(start, end, lineDiffItem.type)
|
|
}
|
|
|
|
// "线"删除
|
|
for (const [lineId, lineDiffItem] of this.lineDiffs.delete.entries()) {
|
|
const start = this.entities.get(lineDiffItem.startId)
|
|
const end = this.entities.get(lineDiffItem.endId)
|
|
|
|
if (!start || !end) {
|
|
// 即使实体不存在也要处理删除
|
|
renderer.deleteLine(
|
|
start || { id: lineDiffItem.startId, t: 'unknown' } as ItemJson,
|
|
end || { id: lineDiffItem.endId, t: 'unknown' } as ItemJson,
|
|
lineDiffItem.type)
|
|
continue
|
|
}
|
|
|
|
if (start.t !== itemTypeName) {
|
|
// 只通知起点对应的渲染器
|
|
continue
|
|
}
|
|
|
|
// 使用安全删除方法, 即使实体不存在也要处理删除
|
|
renderer.deleteLine(start, end, lineDiffItem.type)
|
|
}
|
|
|
|
renderer.endRendererUpdate()
|
|
}
|
|
|
|
// 将 lineDiffs 中新的关系网数据, 更新到实体点里
|
|
const needUpdateIds = new Set<string>()
|
|
for (const [lineId, lineDiffItem] of this.lineDiffs.create.entries()) {
|
|
needUpdateIds.add(lineDiffItem.startId)
|
|
needUpdateIds.add(lineDiffItem.endId)
|
|
}
|
|
for (const [lineId, lineDiffItem] of this.lineDiffs.update.entries()) {
|
|
needUpdateIds.add(lineDiffItem.startId)
|
|
needUpdateIds.add(lineDiffItem.endId)
|
|
}
|
|
for (const [lineId, lineDiffItem] of this.lineDiffs.delete.entries()) {
|
|
needUpdateIds.add(lineDiffItem.startId)
|
|
needUpdateIds.add(lineDiffItem.endId)
|
|
}
|
|
for (const id of needUpdateIds) {
|
|
const entity = this.entities.get(id)
|
|
if (entity) {
|
|
entity.dt.center = Array.from(this.relationIndex.get(id)?.center || [])
|
|
entity.dt.in = Array.from(this.relationIndex.get(id)?.input || [])
|
|
entity.dt.out = Array.from(this.relationIndex.get(id)?.output || [])
|
|
}
|
|
}
|
|
|
|
this.viewport.endViewUpdate()
|
|
this.isUpdating = false
|
|
}
|
|
|
|
/**
|
|
* 更新关系关系网, 计算出差值, 可以临时放在 diffRenderer 中, 等待 commitUpdate 时统一处理
|
|
*/
|
|
private updateRelations(entity: ItemJson, originEntity?: ItemJson): void {
|
|
const { id, dt } = entity
|
|
if (!id || !dt) return
|
|
|
|
const oldCenter = new Set(originEntity?.dt?.center || [])
|
|
const oldIn = new Set(originEntity?.dt?.in || [])
|
|
const oldOut = new Set(originEntity?.dt?.out || [])
|
|
|
|
const newCenter = new Set(dt.center || [])
|
|
const newIn = new Set(dt.in || [])
|
|
const newOut = new Set(dt.out || [])
|
|
|
|
// 更新正向关系
|
|
const relations = this.relationIndex.get(id) || new Relation()
|
|
relations.center = newCenter
|
|
relations.input = newIn
|
|
relations.output = newOut
|
|
this.relationIndex.set(id, relations)
|
|
|
|
// 更新反向关系
|
|
this.updateReverseRelations(id, oldCenter, newCenter, 'center')
|
|
this.updateReverseRelations(id, oldIn, newIn, 'out') // 入边的反向是出边
|
|
this.updateReverseRelations(id, oldOut, newOut, 'in') // 出边的反向是入边
|
|
|
|
// 更新线差量
|
|
this.calculateLineDiffs(id, oldCenter, newCenter, 'center')
|
|
this.calculateLineDiffs(id, oldIn, newIn, 'in')
|
|
this.calculateLineDiffs(id, oldOut, newOut, 'out')
|
|
}
|
|
|
|
private updateReverseRelations(id: string, oldIds: Set<string>, newIds: Set<string>, relationType: LinkType) {
|
|
// 确保关系索引存在
|
|
if (!this.relationIndex.has(id)) {
|
|
this.relationIndex.set(id, new Relation())
|
|
}
|
|
|
|
// 移除旧关系
|
|
for (const relatedId of oldIds) {
|
|
if (!newIds.has(relatedId)) {
|
|
const rev = this.relationIndex.get(relatedId)
|
|
rev.delete(relationType, id)
|
|
this.writeBackEntities.add(relatedId)
|
|
}
|
|
}
|
|
|
|
// 添加新关系
|
|
for (const relatedId of newIds) {
|
|
if (!oldIds.has(relatedId)) {
|
|
let rev = this.relationIndex.get(relatedId)
|
|
if (!rev) {
|
|
rev = new Relation()
|
|
this.relationIndex.set(relatedId, rev)
|
|
}
|
|
rev.add(relationType, id)
|
|
this.writeBackEntities.add(relatedId)
|
|
}
|
|
}
|
|
}
|
|
|
|
private calculateLineDiffs(id: string, oldIds: Set<string>, newIds: Set<string>, lineType: LinkType) {
|
|
// 确保关系索引存在
|
|
if (!this.relationIndex.has(id)) return
|
|
|
|
// 删除被移除的线
|
|
for (const relatedId of oldIds) {
|
|
if (!newIds.has(relatedId)) {
|
|
const lineId = getLineId(id, relatedId, lineType)
|
|
|
|
// 如果这条线已经在 update 列表中,则跳过 delete
|
|
if (this.lineDiffs.update.has(lineId)) continue
|
|
console.log(`[delete] ${id} -> ${relatedId} [${lineType}] => ${lineId}`)
|
|
this.lineDiffs.delete.set(lineId, { startId: id, endId: relatedId, type: lineType })
|
|
}
|
|
}
|
|
|
|
// 新增新增的线
|
|
for (const relatedId of newIds) {
|
|
if (!oldIds.has(relatedId)) {
|
|
const lineId = getLineId(id, relatedId, lineType)
|
|
|
|
// 如果这条线已经在 update 列表中,则跳过 create
|
|
if (this.lineDiffs.update.has(lineId)) continue
|
|
|
|
this.lineDiffs.create.set(lineId, { startId: id, endId: relatedId, type: lineType })
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* 删除关系关系网, 计算出差值, 可以临时放在 diffRenderer 中, 等待 commitUpdate 时统一处理
|
|
*/
|
|
private removeRelations(id: string): void {
|
|
const relations = this.relationIndex.get(id)
|
|
if (!relations) return
|
|
|
|
relations.center.forEach(relatedId => {
|
|
const rev = this.relationIndex.get(relatedId)
|
|
if (rev) rev.center.delete(id)
|
|
this.writeBackEntities.add(relatedId)
|
|
})
|
|
|
|
relations.input.forEach(relatedId => {
|
|
const rev = this.relationIndex.get(relatedId)
|
|
if (rev) rev.output.delete(id)
|
|
this.writeBackEntities.add(relatedId)
|
|
})
|
|
|
|
relations.output.forEach(relatedId => {
|
|
const rev = this.relationIndex.get(relatedId)
|
|
if (rev) rev.input.delete(id)
|
|
this.writeBackEntities.add(relatedId)
|
|
})
|
|
|
|
this.relationIndex.delete(id)
|
|
}
|
|
|
|
private getDiffRenderer(type: string) {
|
|
let renderer = this.diffRenderer.get(type)
|
|
if (typeof renderer === 'undefined') {
|
|
renderer = getRenderer(type)
|
|
renderer.beginRendererUpdate(this.viewport)
|
|
this.diffRenderer.set(type, renderer)
|
|
}
|
|
return renderer
|
|
}
|
|
|
|
|
|
// /**
|
|
// * 重命名一个点
|
|
// * 注意, 不能在更新时刻改名. 所有的关系节点都应该改名
|
|
// */
|
|
// renamePoint(newId: string, originId: string) {
|
|
// if (this.isUpdating) {
|
|
// throw new Error('Cannot rename point during update')
|
|
// }
|
|
// const entity = this.entities.get(originId)
|
|
// if (!entity) {
|
|
// throw new Error(`Entity with id ${originId} does not exist`)
|
|
// }
|
|
// if (this.entities.has(newId)) {
|
|
// throw new Error(`Entity with id ${newId} already exists`)
|
|
// }
|
|
// entity.id = newId
|
|
// this.entities.set(newId, entity)
|
|
// this.entities.delete(originId)
|
|
// this.objects.set(newId, this.objects.get(originId) || [])
|
|
// this.objects.delete(originId)
|
|
//
|
|
// // 更新关系索引
|
|
// const relations = this.relationIndex.get(originId)
|
|
// if (relations) {
|
|
// this.relationIndex.delete(originId)
|
|
//
|
|
// // 更新所有关系中的 id
|
|
// relations.center.forEach((relatedId) => {
|
|
// const rev = this.relationIndex.get(relatedId)
|
|
// if (rev && rev.delete('center', originId)) {
|
|
// rev.add('center', newId)
|
|
// }
|
|
// })
|
|
// relations.input.forEach((relatedId) => {
|
|
// const rev = this.relationIndex.get(relatedId)
|
|
// if (rev && rev.delete('out', originId)) {
|
|
// rev.add('out', newId)
|
|
// }
|
|
// })
|
|
// relations.output.forEach((relatedId) => {
|
|
// const rev = this.relationIndex.get(relatedId)
|
|
// if (rev && rev.delete('in', originId)) {
|
|
// rev.add('in', newId)
|
|
// }
|
|
// })
|
|
//
|
|
// this.relationIndex.set(newId, relations)
|
|
// }
|
|
//
|
|
// // 更新所有线段数据
|
|
// for (const [lineId, lineObjects] of this.lines.entries()) {
|
|
// const [type, startId, endId] = parseLineId(lineId)
|
|
// if (startId === originId) {
|
|
// const newLineId = getLineId(newId, endId, type)
|
|
// this.lines.set(newLineId, lineObjects)
|
|
// this.lines.delete(lineId)
|
|
//
|
|
// } else if (endId === originId) {
|
|
// const newLineId = getLineId(startId, newId, type)
|
|
// this.lines.set(newLineId, lineObjects)
|
|
// this.lines.delete(lineId)
|
|
// }
|
|
// }
|
|
// }
|
|
|
|
deleteEntityOnly(id: string) {
|
|
return this.entities.delete(id)
|
|
}
|
|
|
|
findObjectsById(id: string) {
|
|
return this.objects.get(id) || []
|
|
}
|
|
|
|
deleteObjectsOnly(id: string) {
|
|
// 删除对象时,也需要从 _selectableObjects 中移除
|
|
const rel = this.objects.get(id)
|
|
if (rel) {
|
|
_.remove(this._selectableObjects, obj => !rel.includes(obj))
|
|
}
|
|
return this.objects.delete(id)
|
|
}
|
|
|
|
appendObject(id: string, points: THREE.Object3D[]) {
|
|
this.objects.set(id, points)
|
|
// 如果是可选中对象,添加到 _selectableObjects 中
|
|
if (points.some(obj => obj.userData.selectable !== false)) {
|
|
this._selectableObjects.push(...points)
|
|
}
|
|
}
|
|
|
|
appendLineObject(id: string, lines: THREE.Object3D[]) {
|
|
this.lines.set(id, lines)
|
|
// 如果是可选中对象,添加到 _selectableObjects 中
|
|
if (lines.some(obj => obj.userData.selectable !== false)) {
|
|
this._selectableObjects.push(...lines)
|
|
}
|
|
}
|
|
|
|
findLineObjectsById(lineId: string): THREE.Object3D[] {
|
|
return this.lines.get(lineId) || []
|
|
}
|
|
|
|
deleteLineObjectOnly(id: string) {
|
|
// 删除线对象时,也需要从 _selectableObjects 中移除
|
|
const rel = this.lines.get(id)
|
|
if (rel) {
|
|
_.remove(this._selectableObjects, obj => !rel.includes(obj))
|
|
}
|
|
return this.lines.delete(id)
|
|
}
|
|
|
|
/**
|
|
* 根据 linkStartPointId 查找对应的 ItemJson
|
|
*/
|
|
findItemById(linkStartPointId: string): ItemJson | undefined {
|
|
if (!linkStartPointId) {
|
|
return
|
|
}
|
|
return this.entities.get(linkStartPointId)
|
|
}
|
|
|
|
getObjectByCanvasMouse(event: MouseEvent): THREE.Object3D[] {
|
|
const _domElement = this.viewport.renderer.domElement
|
|
const rect = _domElement.getBoundingClientRect()
|
|
const _pointer = new Vector2()
|
|
_pointer.x = (event.clientX - rect.left) / rect.width * 2 - 1
|
|
_pointer.y = -(event.clientY - rect.top) / rect.height * 2 + 1
|
|
this.viewport.raycaster.setFromCamera(_pointer, this.viewport.camera)
|
|
const _intersections = this.viewport.raycaster.intersectObjects(this._selectableObjects, true)
|
|
|
|
if (!_intersections || _intersections.length === 0) {
|
|
return []
|
|
}
|
|
|
|
// 根据距离排序射线命中的对象集
|
|
return _.map(
|
|
_intersections.sort((a, b) => a.distance - b.distance),
|
|
r => getClosestObject(r.object)
|
|
).filter(obj => obj?.userData && obj.userData.selectable !== false)
|
|
}
|
|
|
|
/**
|
|
* 获取指定范围内的所有对象
|
|
*/
|
|
getObjectsInBox(startX: number, startZ: number, endX: number, endZ: number) {
|
|
const box = new THREE.Box2(
|
|
new THREE.Vector2(startX, startZ),
|
|
new THREE.Vector2(endX, endZ)
|
|
)
|
|
const objectsInBox: THREE.Object3D[] = []
|
|
|
|
for (const [id, objects] of this.objects.entries()) {
|
|
for (const obj of objects) {
|
|
if (box.containsPoint(new Vector2(obj.position.x, obj.position.z))) {
|
|
objectsInBox.push(obj)
|
|
}
|
|
}
|
|
}
|
|
|
|
return objectsInBox
|
|
}
|
|
}
|
|
|
|
interface LineDiffItem {
|
|
startId: string
|
|
endId: string
|
|
type: LinkType
|
|
}
|
|
|
|
|
|
/**
|
|
* 关系详情
|
|
*/
|
|
export class Relation {
|
|
center = new Set<string>()
|
|
input = new Set<string>()
|
|
output = new Set<string>()
|
|
|
|
add(type: LinkType, id: string) {
|
|
if (type === 'in')
|
|
return this.input.add(id)
|
|
else if (type === 'out')
|
|
return this.output.add(id)
|
|
else if (type === 'center')
|
|
return this.center.add(id)
|
|
else
|
|
throw new Error(`Unknown link type: ${type}`)
|
|
}
|
|
|
|
delete(type: LinkType, id: string) {
|
|
if (type === 'in')
|
|
return this.input.delete(id)
|
|
else if (type === 'out')
|
|
return this.output.delete(id)
|
|
else if (type === 'center')
|
|
return this.center.delete(id)
|
|
else
|
|
throw new Error(`Unknown link type: ${type}`)
|
|
}
|
|
}
|
|
|