|
|
|
@ -1,37 +1,40 @@ |
|
|
|
import * as THREE from 'three' |
|
|
|
import type Viewport from '@/core/engine/Viewport' |
|
|
|
import type BaseRenderer from '@/core/base/BaseRenderer' |
|
|
|
import { getRenderer } from './ModuleManager' |
|
|
|
import type Viewport from '@/core/engine/Viewport.ts' |
|
|
|
import { getLineId } from '@/core/ModelUtils' |
|
|
|
|
|
|
|
/** |
|
|
|
* 缓存所有实体和他们的关系, 在各个组件的渲染器会调用这个实体管理器, 进行检索 / 关系 / 获取差异等计算 |
|
|
|
* 实体管理器 |
|
|
|
* 缓存所有 数据(ItemJson)和他们的关系, 以及渲染对象 THREE.Object3D |
|
|
|
* 数据更新的流程是: |
|
|
|
* 1. 状态管理器 StateManager 发出数据被修改的请求, 这些请求通常只有 ItemJson 数据 |
|
|
|
* 2. 实体管理器 (EntityManager) 需要计算所有相关的点和线的变化. 在 endUpdate 时需要根据 center / in / out 关系, 计算出: 点的创建 / 点的更新 / 点的删除 / 线的创建 / 线的更新 / 线的删除 |
|
|
|
* 3. 通知对应的渲染器 (Renderer) 进行点和线的创建 / 更新 / 删除 |
|
|
|
*/ |
|
|
|
export default class EntityManager { |
|
|
|
/** |
|
|
|
* 视窗对象, 所有状态管理器, ThreeJs场景,控制器,摄像机, 实体管理器都在这里 |
|
|
|
*/ |
|
|
|
viewport: Viewport |
|
|
|
|
|
|
|
/** |
|
|
|
* 所有数据点的实体 |
|
|
|
*/ |
|
|
|
entities = new Map<string, ItemJson>() |
|
|
|
// 所有数据点的实体
|
|
|
|
readonly entities = new Map<string, ItemJson>() |
|
|
|
|
|
|
|
/** |
|
|
|
* 所有数据点与 THREEJS 对象的关系 |
|
|
|
*/ |
|
|
|
objects = new Map<string, THREE.Object3D[]>() |
|
|
|
// 关系索引
|
|
|
|
readonly relationIndex = new Map<string, Relation>() |
|
|
|
|
|
|
|
/** |
|
|
|
* 所有关联关系 |
|
|
|
*/ |
|
|
|
relationIndex = new Map<string, { center: Set<string>; in: Set<string>; out: Set<string> }>() |
|
|
|
// 所有 THREEJS "点"对象
|
|
|
|
readonly objects = new Map<string, THREE.Object3D[]>() |
|
|
|
|
|
|
|
/** |
|
|
|
* 两两关联关系与 THREEJS 对象之间的关联 |
|
|
|
*/ |
|
|
|
lines = new Map<string, THREE.Object3D[]>() |
|
|
|
// 所有 THREEJS "线"对象
|
|
|
|
readonly lines = new Map<string, THREE.Object3D[]>() |
|
|
|
|
|
|
|
private batchMode = false |
|
|
|
// 差量渲染器
|
|
|
|
readonly diffRenderer = new Map<string, BaseRenderer>() |
|
|
|
// 线差量记录
|
|
|
|
lineDiffs = { |
|
|
|
create: new Map<string, LineDiffItem>(), |
|
|
|
update: new Map<string, LineDiffItem>(), |
|
|
|
delete: new Map<string, LineDiffItem>() |
|
|
|
} |
|
|
|
|
|
|
|
init(viewport: Viewport) { |
|
|
|
this.viewport = viewport |
|
|
|
@ -41,48 +44,51 @@ export default class EntityManager { |
|
|
|
* 批量更新开始 |
|
|
|
*/ |
|
|
|
beginUpdate(): void { |
|
|
|
this.batchMode = true |
|
|
|
this.viewport.beginSync() |
|
|
|
this.viewport.beginUpdate() |
|
|
|
this.diffRenderer.clear() |
|
|
|
this.lineDiffs.create.clear() |
|
|
|
this.lineDiffs.update.clear() |
|
|
|
this.lineDiffs.delete.clear() |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* 创建一个实体, 这个点的 center[] / in[] / out[] 关联的点, 都要对应进行关联 |
|
|
|
* 创建或更新一个实体, 这个点的 center[] / in[] / out[] 关联的点, 可能都要对应进行关联 |
|
|
|
*/ |
|
|
|
createEntity(entity: ItemJson, option?: EntityCudOption): void { |
|
|
|
if (this.entities.has(entity.id!)) { |
|
|
|
throw new Error(`Entity with ID "${entity.id}" already exists.`) |
|
|
|
createOrUpdateEntity(entity: ItemJson, option: EntityCudOption = {}): void { |
|
|
|
if (!entity?.id) { |
|
|
|
throw new Error('Entity must have an id') |
|
|
|
} |
|
|
|
this.entities.set(entity.id!, entity) |
|
|
|
this.updateRelations(entity) |
|
|
|
const renderer = getRenderer(entity.t) |
|
|
|
renderer.createPoint(entity, option) |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* 更新实体, 他可能更新位置, 也可能更新颜色, 也可能修改 dt.center[] / dt.in[] / dt.out[] 修正与其他点之间的关联 |
|
|
|
*/ |
|
|
|
updateEntity(entity: ItemJson, option?: EntityCudOption): void { |
|
|
|
if (!this.entities.has(entity.id!)) { |
|
|
|
throw new Error(`Entity with ID "${entity.id}" does not exist.`) |
|
|
|
// 找到这个数据的渲染器
|
|
|
|
const renderer = this.getDiffRenderer(entity.t) |
|
|
|
|
|
|
|
const originEntity = this.entities.get(entity.id) |
|
|
|
this.entities.set(entity.id, entity) |
|
|
|
|
|
|
|
// 更新关系网
|
|
|
|
this.updateRelations(entity, originEntity) |
|
|
|
|
|
|
|
if (typeof originEntity === 'undefined') { |
|
|
|
renderer.createPoint(entity, option) |
|
|
|
|
|
|
|
} else { |
|
|
|
option.originEntity = _.cloneDeep(originEntity) |
|
|
|
renderer.updatePoint(entity, option) |
|
|
|
} |
|
|
|
this.entities.set(entity.id!, entity) |
|
|
|
this.updateRelations(entity) |
|
|
|
const renderer = getRenderer(entity.t) |
|
|
|
renderer.updatePoint(entity, option) |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* 删除实体, 与这个点有关的所有线都要删除, 与这个点有关系的点,都要对应的 center[] / in[] / out[] 都要断绝关系 |
|
|
|
*/ |
|
|
|
deleteEntity(id: string, option?: EntityCudOption): void { |
|
|
|
deleteEntity(id: string, option: EntityCudOption = {}): void { |
|
|
|
const entity = this.entities.get(id) |
|
|
|
if (!entity) { |
|
|
|
throw new Error(`Entity with ID "${id}" does not exist.`) |
|
|
|
} |
|
|
|
if (!entity) return |
|
|
|
|
|
|
|
option.originEntity = _.cloneDeep(entity) |
|
|
|
|
|
|
|
this.entities.delete(id) |
|
|
|
this.removeRelations(id) |
|
|
|
const renderer = getRenderer(entity.t) |
|
|
|
renderer.deletePoint(id, option) |
|
|
|
this.getDiffRenderer(entity.t).deletePoint(id, option) |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
@ -92,66 +98,216 @@ export default class EntityManager { |
|
|
|
* - 如果进行了删除, 与这个点有关的所有线都要删除, 与这个点有关系的点,都要对应的 center[] / in[] / out[] 都要断绝关系 |
|
|
|
* - 如果进行了更新, 如果改了颜色/位置, 则需要在UI上进行对应修改,如果改了关系,需要与关联的节点批量调整 |
|
|
|
* 将影响到的所有数据, 都变成一个修改集合, 统一调用对应单元类型渲染器(BaseRenderer)的 createPoint / deletePoint / updatePoint / createLine / updateLine / deleteLine 方法 |
|
|
|
* 具体方法就是 viewport.getItemTypeRenderer(itemTypeName) |
|
|
|
*/ |
|
|
|
commitUpdate(): void { |
|
|
|
this.batchMode = false |
|
|
|
this.viewport.endSync() |
|
|
|
} |
|
|
|
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.t !== itemTypeName) { |
|
|
|
// 只通知起点对应的渲染器
|
|
|
|
continue |
|
|
|
} |
|
|
|
renderer.createLine(start, end, lineDiffItem.type) |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* 获取实体 |
|
|
|
*/ |
|
|
|
getEntity(id: string): ItemJson | undefined { |
|
|
|
return this.entities.get(id) |
|
|
|
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.t !== itemTypeName) { |
|
|
|
// 只通知起点对应的渲染器
|
|
|
|
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.t !== itemTypeName) { |
|
|
|
// 只通知起点对应的渲染器
|
|
|
|
continue |
|
|
|
} |
|
|
|
renderer.deleteLine(start, end, lineDiffItem.type) |
|
|
|
} |
|
|
|
|
|
|
|
renderer.endUpdate() |
|
|
|
} |
|
|
|
this.viewport.endUpdate() |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* 获取相关实体 |
|
|
|
* 更新关系关系网, 计算出差值, 可以临时放在 diffRenderer 中, 等待 commitUpdate 时统一处理 |
|
|
|
*/ |
|
|
|
getRelatedEntities(id: string, relationType: 'center' | 'in' | 'out'): ItemJson[] { |
|
|
|
const relations = this.relationIndex.get(id)?.[relationType] || new Set() |
|
|
|
return Array.from(relations).map((relatedId) => this.entities.get(relatedId)!) |
|
|
|
} |
|
|
|
|
|
|
|
private updateRelations(entity: ItemJson): void { |
|
|
|
private updateRelations(entity: ItemJson, originEntity?: ItemJson): void { |
|
|
|
const { id, dt } = entity |
|
|
|
if (!id || !dt) return |
|
|
|
|
|
|
|
const relations = this.relationIndex.get(id) || { center: new Set(), in: new Set(), out: new Set() } |
|
|
|
relations.center = new Set(dt.center || []) |
|
|
|
relations.in = new Set(dt.in || []) |
|
|
|
relations.out = new Set(dt.out || []) |
|
|
|
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) |
|
|
|
|
|
|
|
// Update reverse relations
|
|
|
|
this.updateReverseRelations(id, dt.center, 'center') |
|
|
|
this.updateReverseRelations(id, dt.in, 'out') |
|
|
|
this.updateReverseRelations(id, dt.out, 'in') |
|
|
|
// 更新反向关系
|
|
|
|
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, relatedIds: string[] | undefined, relationType: 'center' | 'in' | 'out'): void { |
|
|
|
if (!relatedIds) return |
|
|
|
relatedIds.forEach((relatedId) => { |
|
|
|
const relatedRelations = this.relationIndex.get(relatedId) || { |
|
|
|
center: new Set(), |
|
|
|
in: new Set(), |
|
|
|
out: new Set() |
|
|
|
private updateReverseRelations(id: string, oldIds: Set<string>, newIds: Set<string>, relationType: LinkType) { |
|
|
|
// 移除旧关系
|
|
|
|
for (const relatedId of oldIds) { |
|
|
|
if (!newIds.has(relatedId)) { |
|
|
|
const rev = this.relationIndex.get(relatedId) |
|
|
|
rev.delete(relationType, id) |
|
|
|
} |
|
|
|
relatedRelations[relationType].add(id) |
|
|
|
this.relationIndex.set(relatedId, relatedRelations) |
|
|
|
}) |
|
|
|
} |
|
|
|
|
|
|
|
// 添加新关系
|
|
|
|
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) |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
private calculateLineDiffs(id: string, oldIds: Set<string>, newIds: Set<string>, lineType: LinkType) { |
|
|
|
// 删除被移除的线
|
|
|
|
for (const relatedId of oldIds) { |
|
|
|
if (!newIds.has(relatedId)) { |
|
|
|
const lineId = getLineId(id, relatedId, lineType) |
|
|
|
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) |
|
|
|
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 |
|
|
|
|
|
|
|
// Remove reverse relations
|
|
|
|
relations.center.forEach((relatedId) => this.relationIndex.get(relatedId)?.center.delete(id)) |
|
|
|
relations.in.forEach((relatedId) => this.relationIndex.get(relatedId)?.out.delete(id)) |
|
|
|
relations.out.forEach((relatedId) => this.relationIndex.get(relatedId)?.in.delete(id)) |
|
|
|
const removeLine = (relatedId: string, type: LinkType) => { |
|
|
|
const lineId = getLineId(id, relatedId, type) |
|
|
|
this.lineDiffs.delete.set(lineId, { startId: id, endId: relatedId, type }) |
|
|
|
} |
|
|
|
|
|
|
|
relations.center.forEach((relatedId) => { |
|
|
|
this.relationIndex.get(relatedId)?.center.delete(id) |
|
|
|
removeLine(relatedId, 'center') |
|
|
|
}) |
|
|
|
|
|
|
|
relations.input.forEach((relatedId) => { |
|
|
|
this.relationIndex.get(relatedId)?.output.delete(id) |
|
|
|
removeLine(relatedId, 'in') |
|
|
|
}) |
|
|
|
|
|
|
|
relations.output.forEach((relatedId) => { |
|
|
|
this.relationIndex.get(relatedId)?.input.delete(id) |
|
|
|
removeLine(relatedId, 'out') |
|
|
|
}) |
|
|
|
|
|
|
|
this.relationIndex.delete(id) |
|
|
|
} |
|
|
|
|
|
|
|
private getDiffRenderer(type: string) { |
|
|
|
let renderer = this.diffRenderer.get(type) |
|
|
|
if (typeof renderer === 'undefined') { |
|
|
|
renderer = getRenderer(type) |
|
|
|
renderer.beginUpdate(this.viewport) |
|
|
|
this.diffRenderer.set(type, renderer) |
|
|
|
} |
|
|
|
return renderer |
|
|
|
} |
|
|
|
|
|
|
|
deleteEntityOnly(id: string) { |
|
|
|
return this.entities.delete(id) |
|
|
|
} |
|
|
|
|
|
|
|
findObjectsById(id: string) { |
|
|
|
return this.objects.get(id) || [] |
|
|
|
} |
|
|
|
|
|
|
|
deleteObjectsOnly(id: string) { |
|
|
|
return this.objects.delete(id) |
|
|
|
} |
|
|
|
|
|
|
|
appendObject(id: string, points: THREE.Object3D[]) { |
|
|
|
this.objects.set(id, points) |
|
|
|
} |
|
|
|
|
|
|
|
appendLineObject(id: string, lines: THREE.Object3D[]) { |
|
|
|
this.lines.set(id, lines) |
|
|
|
} |
|
|
|
|
|
|
|
findLineObjectsById(lineId: string): THREE.Object3D[] { |
|
|
|
return this.lines.get(lineId) || [] |
|
|
|
} |
|
|
|
|
|
|
|
deleteLineObjectOnly(id: string) { |
|
|
|
return this.lines.delete(id) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
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') |
|
|
|
this.input.add(id) |
|
|
|
else if (type === 'out') |
|
|
|
this.output.add(id) |
|
|
|
else if (type === 'center') |
|
|
|
this.center.add(id) |
|
|
|
else |
|
|
|
throw new Error(`Unknown link type: ${type}`) |
|
|
|
} |
|
|
|
|
|
|
|
delete(type: LinkType, id: string) { |
|
|
|
if (type === 'in') |
|
|
|
this.input.delete(id) |
|
|
|
else if (type === 'out') |
|
|
|
this.output.delete(id) |
|
|
|
else if (type === 'center') |
|
|
|
this.center.delete(id) |
|
|
|
else |
|
|
|
throw new Error(`Unknown link type: ${type}`) |
|
|
|
} |
|
|
|
} |