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.
 
 
 

649 lines
18 KiB

import _ from 'lodash'
import * as THREE from 'three'
import localforage from 'localforage'
import type EntityManager from './EntityManager'
import { markRaw, reactive, ref } from 'vue'
import type Viewport from '@/core/engine/Viewport.ts'
import { getFreezeDeep, getQueryParams, setQueryParam } from '@/utils/webutils.ts'
import { ensureEntityRelationsConsistency } from '@/core/ModelUtils.ts'
import EventBus from '@/runtime/EventBus.ts'
import { Vector2 } from 'three/src/math/Vector2'
// 差异类型定义
interface DataDiff {
added: ItemJson[]
removed: ItemJson[]
updated: { before: ItemJson; after: ItemJson }[]
}
// 历史记录项
interface HistoryStep {
diff: DataDiff
timestamp: number
}
/**
* 数据状态管理器
* 他能够对数据进行载入, 保存草稿, 载入草稿, 数据增删改查,撤销、重做等操作
* 最终他会将数据的变化同步到 EntityManager 中
* 主要功能包括:
* 1. 管理场景数据的读取、保存, 以及临时保存、临时读取等功能
* 2. 管理撤销、重做功能
* 3. 交互控制组件通过 update() 方法修改数据
* this.viewport.stateManager.update(({ getEntity, putEntity, deleteEntity, addEntity }) => {
* const entity = getEntity(id) // 获取实体
* entity.abc = 123
* putEntity(entity) // 提交修改
*
* deleteEntity(id) // 删除实体
* addEntity(newEntity) // 添加新实体
* })
* 4. 内部如果进行了撤销、还原等操作,会通过 syncDataState() 方法将 vdata 数据与 EntityManager 进行同步
* 5. 注意,如果正在读取中,需要设置 isLoading = true,外部需要等待加载完成后再进行操作
*
* 主要难点:
* - 单张地图数据量可能超过 10000 个对象, 需要高效的管理数据状态
*/
export default class StateManager {
/**
* 唯一场景标识符, 用于做临时存储的 key
*/
readonly id: string
/**
* 实体对象, 用于同步当前场景的状态
*/
entityManager: EntityManager
viewport: Viewport
/**
* 是否发生了变化,通知外部是否需要保存数据
*/
readonly isChanged = ref(false)
/**
* 是否正在加载数据,通知外部是否需要等待加载完成
*/
readonly isLoading = ref(false)
readonly storeKey: string
/**
* 当前场景数据
*/
private ___vdata: VData
/**
* 使用循环缓冲区存储历史记录
*/
private historySteps: HistoryStep[] = []
private historyIndex = -1
private maxHistorySteps = 20
// 标记是否有未保存的更改
private pendingChanges = false
private isAutoSavingPaused = false
private autoSaveTimer: number | null = null
private isUpdating = false
// 变化追踪器
private readonly changeTracker: DataDiff = {
added: [],
removed: [],
updated: []
}
/**
* @param id 唯一场景标识符, 用于做临时存储的 key
* @param maxHistorySteps 最大回撤步数 默认为 20
*/
constructor(id: string, maxHistorySteps = 20) {
this.id = id
this.storeKey = `-tmp-yvan-lcc-${this.id}`
this.maxHistorySteps = maxHistorySteps
this.historySteps = []
this.pendingChanges = false
this.isAutoSavingPaused = false
}
init(viewport: Viewport) {
this.viewport = viewport
this.entityManager = viewport.entityManager
_.defer(() => {
this.fullSync(false)
})
}
// 差异反转方法
private invertDiff(diff: DataDiff): DataDiff {
return {
added: diff.removed,
removed: diff.added,
updated: diff.updated.map(u => ({ before: u.after, after: u.before }))
}
}
/**
* 将当前数据 与 entityManager 进行同步, 对比出不同的部分,分别进行更新
* - 调用 entityManager.beginEntityUpdate() 开始更新
* - 调用 entityManager.createOrUpdateEntity(vdataItem) 添加场景中新的实体
* - 调用 entityManager.deleteEntity(id) 删除场景中的实体
* - 调用 entityManager.endEntityUpdate() 结束更新场景
*/
syncDataState(diff: DataDiff): void {
// 没有变化时跳过同步
if (
diff.added.length === 0 &&
diff.removed.length === 0 &&
diff.updated.length === 0
) {
return
}
console.log('[StateManager] 同步数据状态,变更:', diff)
let needUpdateSelectBox = false
let needDeleteSelectBox = false
let needUpdateMulitSelectBox = false
this.entityManager.beginEntityUpdate()
for (const item of diff.added) {
this.entityManager.createOrUpdateEntity(item)
}
for (const { after } of diff.updated) {
this.entityManager.createOrUpdateEntity(after)
// 如果更新的对象 id 包含当前选中的
if (after.id === this.viewport.state.selectedEntityId) {
needUpdateSelectBox = true
}
if (_.includes(this.viewport.state.multiSelectedEntityIds, after.id)) {
needUpdateMulitSelectBox = true
}
}
// 最后处理删除
for (const item of diff.removed) {
this.entityManager.deleteEntity(item.id)
if (item.id === this.viewport.state.selectedEntityId) {
needDeleteSelectBox = true
}
if (_.includes(this.viewport.state.multiSelectedEntityIds, item.id)) {
needUpdateMulitSelectBox = true
}
}
this.entityManager.endEntityUpdate()
// 更新选中框
if (needDeleteSelectBox) {
this.viewport.selectManager.selectById(null)
} else if (needUpdateSelectBox) {
this.viewport.selectManager.selectById(this.viewport.state.selectedEntityId)
}
// 重新计算并更新多选框
if (needUpdateMulitSelectBox) {
const newMultiSelectedEntityIds = []
for (const entityId of this.viewport.state.multiSelectedEntityIds) {
if (this.entityManager.findItemById(entityId)) {
newMultiSelectedEntityIds.push(entityId)
}
}
this.viewport.selectManager.multiSelectByIds(newMultiSelectedEntityIds)
}
// 获取被改过的数据, 覆盖之前的数据
const writeBackMap = this.entityManager.cloneWriteBackEntities()
for (let i = 0; i < this.___vdata.items.length; i++) {
const item = this.___vdata.items[i]
if (writeBackMap.has(item.id)) {
this.___vdata.items[i] = writeBackMap.get(item.id)
}
}
}
/**
* 撤销
*/
undo() {
const viewport: Viewport = window['viewport']
if (!viewport) {
system.msg('没有找到当前视图')
return
}
if (!this.undoEnabled()) return
const step = this.historySteps[this.historyIndex]
if (!step) return
// 生成和应用反转差异
const reverseDiff = this.invertDiff(step.diff)
this.applyDiff(reverseDiff)
this.syncDataState(reverseDiff)
this.historyIndex--
this.isChanged.value = true
this.pendingChanges = true
system.msg('撤销完成')
// if (viewport.state.multiSelectedObjects.length > 0) {
// EventBus.dispatch('multiSelectedObjectsChanged', {
// multiSelectedObjects: viewport.state.multiSelectedObjects
// })
// }
// if (viewport.state.selectedObject) {
// EventBus.dispatch('selectedObjectPropertyChanged', {
// selectedObject: viewport.state.selectedObject
// })
// }
this.startAutoSave()
}
/**
* 重做
*/
redo() {
const viewport: Viewport = window['viewport']
if (!viewport) {
system.msg('没有找到当前视图')
return
}
if (!this.redoEnabled()) return
this.historyIndex++
const step = this.historySteps[this.historyIndex]
if (!step) return
this.applyDiff(step.diff)
this.syncDataState(step.diff)
this.isChanged.value = true
this.pendingChanges = true
system.msg('重做完成')
// if (viewport.state.multiSelectedObjects.length > 0) {
// EventBus.dispatch('multiSelectedObjectsChanged', {
// multiSelectedObjects: viewport.state.multiSelectedObjects
// })
// }
// if (viewport.state.selectedObject) {
// EventBus.dispatch('selectedObjectPropertyChanged', {
// selectedObject: viewport.state.selectedObject
// })
// }
this.startAutoSave()
}
// 差异应用方法
private applyDiff(diff: DataDiff) {
const { added, removed, updated } = diff
// 删除
const removedIds = new Set(removed.map(item => item.id))
_.remove(this.___vdata.items, item => removedIds.has(item.id))
// 新增
const addedIds = new Set(added.map(i => i.id))
_.remove(this.___vdata.items, item => addedIds.has(item.id))
this.___vdata.items.push(...added)
// 更新
const updateMap = new Map(updated.map(u => [u.after.id, u.after]))
for (const [key, after] of updateMap) {
const idx = _.findIndex(this.___vdata.items, (item => item.id === key))
if (idx >= 0) {
Object.assign(this.___vdata.items[idx], after)
} else {
this.___vdata.items.push(after)
}
}
}
/**
* 完整读取数据
* @private
*/
private fullSync(isAutoSave = true) {
// 修补关系数据, 确保 center / in / out 关系满足一致性
this.___vdata.items = ensureEntityRelationsConsistency(this.___vdata.items)
this.entityManager.beginEntityUpdate()
this.___vdata.items.forEach(item => {
this.entityManager.createOrUpdateEntity(item)
})
this.entityManager.endEntityUpdate()
// 初始状态作为第一步
this.pendingChanges = false
if (isAutoSave) {
this.startAutoSave()
}
}
undoEnabled() {
return this.historyIndex >= 0
}
redoEnabled() {
const nextIndex = (this.historyIndex + 1) % this.maxHistorySteps
return !!this.historySteps[nextIndex]
}
/**
* 从外部加载数据
*/
async load(data: VData) {
this.isLoading.value = true
this.historySteps = Array.from({ length: this.maxHistorySteps }, () => null)
this.historyIndex = -1
try {
// 停止自动保存,避免在加载过程中触发
this.stopAutoSave()
this.___vdata = data
// 同步到视口
// this.fullSync()
this.isChanged.value = false
console.log('[StateManager] 加载完成,共', data.items.length, '个对象')
// 强制保存一次初始状态
await this.saveToLocalstore()
} finally {
EventBus.dispatch('dataLoadComplete', {
stateManager: this
})
this.isLoading.value = false
}
}
startAutoSave() {
this.isAutoSavingPaused = false
if (this.autoSaveTimer !== null) {
window.clearTimeout(this.autoSaveTimer)
this.autoSaveTimer = null
}
// 设置新的定时器,在 5 秒后尝试保存
this.queueAutoSave()
}
stopAutoSave() {
if (this.autoSaveTimer !== null) {
window.clearTimeout(this.autoSaveTimer)
this.autoSaveTimer = null
}
this.isAutoSavingPaused = true
}
private queueAutoSave() {
if (this.autoSaveTimer !== null || this.isAutoSavingPaused) return
if (!this.pendingChanges) return
this.autoSaveTimer = window.setTimeout(async () => {
this.autoSaveTimer = null
if (this.isUpdating) {
const checkInterval = setInterval(() => {
if (!this.isUpdating) {
clearInterval(checkInterval)
if (!this.isAutoSavingPaused) {
this.queueAutoSave()
}
} else {
console.log('wait for entityManager to finish updating...')
}
}, 200)
return
}
await this.saveToLocalstore()
}, 2000)
}
/**
* 保存数据到外部
*/
async save(): Promise<Object> {
return _.cloneDeep(this.___vdata)
}
/**
* 保存到本地存储 浏览器indexDb(防止数据丢失)
*/
async saveToLocalstore() {
await localforage.setItem(this.storeKey, this.___vdata)
console.log('[StateManager] 数据已保存草稿:' + this.storeKey + ', 共', this.___vdata.items.length, '个对象')
}
/**
* 从本地存储还原数据
*/
async loadFromLocalstore(): Promise<boolean> {
try {
this.isLoading.value = true
const saved: VData = await localforage.getItem(this.storeKey)
if (saved) {
this.___vdata = {
...saved
}
this.isChanged.value = saved.isChanged || false
// this.fullSync(false) // 同步到视口
console.log('[StateManager] 从本地存储 [' + this.storeKey + '] 恢复', this.___vdata.items.length, '个对象')
return true
}
return false
} catch (error) {
console.error('[StateManager] 从本地存储加载失败:', error)
return false
} finally {
this.isLoading.value = false
EventBus.dispatch('dataLoadComplete', {
stateManager: this
})
}
}
/**
* 删除本地存储
*/
async removeLocalstore() {
try {
await localforage.removeItem(this.storeKey)
console.log('[StateManager] 本地存储已清除')
} catch (error) {
console.error('[StateManager] 清除本地存储失败:', error)
}
}
/**
* 销毁资源
*/
dispose() {
// 清理引用
delete this.___vdata
delete this.historySteps
}
/**
* 尝试从草稿中读取数据
*/
static async tryLoadCatalogFromLocalstore(stateManagerId: string): Promise<VData | null> {
// 获取 url 中的 stateManagerId
// 从 localforage 中读取草稿数据
if (stateManagerId) {
const storeKey = `-tmp-yvan-lcc-${stateManagerId}`
const saved: VData = await localforage.getItem(storeKey)
if (saved && saved.catalog && saved.catalogCode && saved.worldData) {
return saved
}
}
return null
}
/**
* 根据 ID 查找实体
*/
findItemById(entityId: string): ItemJson | undefined {
return getFreezeDeep(_.find(this.___vdata.items, item => item.id === entityId))
}
/**
* 根据位置查找实体, 可带类型
*/
findItemByPosition(point: THREE.Vector3, itemTypeName?: string): ItemJson | undefined {
for (const item of this.___vdata.items) {
if (item.tf?.[0]?.[0] === point.x && item.tf?.[0]?.[2] === point.z) {
if (!itemTypeName || item.t === itemTypeName) {
return getFreezeDeep(item)
}
}
}
}
/**
* 使用 updater 函数更新状态, 并同步转换为实体和渲染
*/
update(updaterFn: (updater: StateUpdater) => void): void {
const changeTracker: DataDiff = {
added: [],
removed: [],
updated: []
}
this.isUpdating = true
const entityMap = new Map(this.___vdata.items.map(item => [item.id, item]))
// 构建 Updater 实例
const updater: StateUpdater = {
getEntity: (id: string) => {
const entity = entityMap.get(id)
if (!entity) throw new Error(`Entity with id ${id} not found`)
return _.cloneDeep(entity)
},
putEntity: (entity: ItemJson) => {
const id = entity.id
const original = entityMap.get(id)
if (!original) throw new Error(`Entity with id ${id} not found for update`)
if (!_.isEqual(original, entity)) {
changeTracker.updated.push({
before: _.cloneDeep(original),
after: _.cloneDeep(entity)
})
entityMap.set(id, _.cloneDeep(entity))
}
},
deleteEntity: (id: string): boolean => {
const original = entityMap.get(id)
if (!original) return
changeTracker.removed.push(_.cloneDeep(original))
return entityMap.delete(id)
},
addEntity: (entity: ItemJson) => {
const id = entity.id
if (entityMap.has(id)) throw new Error(`Entity with id ${id} already exists`)
changeTracker.added.push(_.cloneDeep(entity))
entityMap.set(id, _.cloneDeep(entity))
}
}
// 停止自动保存,避免在更新过程中触发
this.stopAutoSave()
// 执行用户传入的修改函数
updaterFn(updater)
// 更新 vdata.items
this.___vdata.items = Array.from(entityMap.values())
// 保存差分到 history 和 sync 到 entityManager
this.saveStepWithDiff(changeTracker)
this.syncDataState(changeTracker)
this.isChanged.value = true
this.pendingChanges = true
this.isUpdating = false
this.startAutoSave()
}
private saveStepWithDiff(diff: DataDiff): void {
if (diff.added.length === 0 && diff.removed.length === 0 && diff.updated.length === 0) {
return
}
const step: HistoryStep = {
diff: {
added: _.cloneDeep(diff.added),
removed: _.cloneDeep(diff.removed),
updated: _.cloneDeep(diff.updated)
},
timestamp: Date.now()
}
if (this.historySteps.length >= this.maxHistorySteps) {
this.historySteps.shift()
}
this.historySteps.push(step)
this.historyIndex = this.historySteps.length - 1
}
/**
* 查找指定点附近指定距离内的所有 stateManager 下的 item 对象
* @param point 点位 x,z 坐标
* @param distance 距离
*/
findStateItemsByDistance(point: Vector2, distance: number): ItemJson[] {
const result: ItemJson[] = []
for (const item of this.___vdata.items) {
// 安全校验 tf 结构
if (!item.tf || !Array.isArray(item.tf) || item.tf[0].length < 3) {
continue
}
const [x, , z] = item.tf[0]
const itemPoint = new Vector2(x, z)
const dist = itemPoint.distanceTo(point)
if (dist <= distance) {
result.push(getFreezeDeep(item))
}
}
// 按距离升序排序(近距离优先)
return result.sort((a, b) => {
const aPos = new Vector2(a.tf[0][0], a.tf[0][2])
const bPos = new Vector2(b.tf[0][0], b.tf[0][2])
return aPos.distanceTo(point) - bPos.distanceTo(point)
})
}
}
export interface StateUpdater {
getEntity(id: string): ItemJson
putEntity(entity: ItemJson): void // 修改已有实体
deleteEntity(id: string): boolean // 删除实体
addEntity(entity: ItemJson): void // 添加新实体
}
export interface StateUpdateOption {
autoSave?: boolean
createFromInteraction?: boolean
}