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 { 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 { 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 { // 获取 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 }