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.
 
 
 

535 lines
14 KiB

import _ from 'lodash'
import localforage from 'localforage'
import type EntityManager from './EntityManager'
import { markRaw, reactive, ref } from 'vue'
import type Viewport from '@/core/engine/Viewport.ts'
import { getQueryParams, setQueryParam } from '@/utils/webutils.ts'
// 差异类型定义
interface DataDiff {
added: ItemJson[]
removed: ItemJson[]
updated: { before: ItemJson; after: ItemJson }[]
}
// 历史记录项
interface HistoryStep {
diff: DataDiff
timestamp: number
}
/**
* 数据状态管理器
* 他能够对数据进行载入, 保存草稿, 载入草稿, 数据增删改查,撤销、重做等操作
* 最终他会将数据的变化同步到 EntityManager 中
* 主要功能包括:
* 1. 管理场景数据的读取、保存, 以及临时保存、临时读取等功能
* 2. 管理撤销、重做功能
* 3. 交互控制组件通过如下步骤修改数据
* - 1. 调用 beginStateUpdate 开始修改数据
* - 2. 直接修改 vdata 数据
* - 3. 调用 endStateUpdate 完成数据修改
* 4. 内部如果进行了撤销、还原等操作,会通过 syncDataState() 方法将 vdata 数据与 EntityManager 进行同步
* 5. 注意,如果正在读取中,需要设置 isLoading = true,外部需要等待加载完成后再进行操作
*
* 主要难点:
* - 单张地图数据量可能超过 10000 个对象, 需要高效的管理数据状态
*
* // 用例1, 修改
* stateManager.beginStateUpdate();stateManager.vdata.items[1].tf[0] = [-10, 0, 4];stateManager.endStateUpdate();
* stateManager.undo()
* stateManager.redo()
*
* // 用例2 添加
* stateManager.beginStateUpdate();stateManager.vdata.items[3].dt.center.push('p5');stateManager.vdata.items.push({ id: 'p5', t: 'measure', tf: [[-6.0, 0, 8], [0, 0, 0], [0.25, 0.1, 0.25]], dt: { center: ['p4'] } });stateManager.endStateUpdate();
* stateManager.undo();
* stateManager.redo();
*
* // 用例3 删除
* stateManager.beginStateUpdate(); stateManager.vdata.items.splice(3, 1); stateManager.endStateUpdate();
* stateManager.undo()
* stateManager.redo()
*
* // 用例4 多连线
* stateManager.beginStateUpdate();stateManager.vdata.items[0].dt.center.push('p3');stateManager.vdata.items[2].dt.center.push('p1');stateManager.endStateUpdate();
* stateManager.undo()
*
* // 用例5 删除线
* stateManager.beginStateUpdate();stateManager.vdata.items[0].dt.center=[];stateManager.vdata.items[1].dt.center.splice(0,1);stateManager.endStateUpdate();
* stateManager.undo()
*/
export default class StateManager {
/**
* 唯一场景标识符, 用于做临时存储的 key
*/
readonly id: string
/**
* 实体对象, 用于同步当前场景的状态
*/
readonly entityManager: EntityManager
/**
* 是否发生了变化,通知外部是否需要保存数据
*/
readonly isChanged = ref(false)
/**
* 是否正在加载数据,通知外部是否需要等待加载完成
*/
readonly isLoading = ref(false)
readonly storeKey: string
/**
* 当前场景数据
*/
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: []
}
/**
* 数据快照(用于差异计算)
*/
private lastStateDict = new Map<string, ItemJson>()
/**
* @param id 唯一场景标识符, 用于做临时存储的 key
* @param viewport 视口对象, 用于获取、同步当前场景的状态
* @param maxHistorySteps 最大回撤步数 默认为 20
*/
constructor(id: string, viewport: Viewport, maxHistorySteps = 20) {
this.id = id
this.storeKey = `-tmp-yvan-lcc-${this.id}`
this.entityManager = viewport.entityManager
this.maxHistorySteps = maxHistorySteps
// this.historySteps = Array.from({ length: this.maxHistorySteps }, () => null)
this.historySteps = []
this.pendingChanges = false
this.isAutoSavingPaused = false
}
/**
* 开始用户操作(创建数据快照)
*/
beginStateUpdate(): void {
this.lastStateDict = new Map(this.vdata.items.map(item => [item.id, _.cloneDeep(item)]))
this.changeTracker.added.length = 0
this.changeTracker.removed.length = 0
this.changeTracker.updated.length = 0
this.isUpdating = true
}
/**
* 结束用户操作(计算差异并保存)
*/
endStateUpdate(option = { autoSave: true }): void {
this.calculateDiff()
this.saveStep()
this.syncDataState(this.changeTracker)
this.isChanged.value = true
this.pendingChanges = true // 标记有需要保存的更改
this.isUpdating = false
if (option.autoSave) {
this.startAutoSave() // 触发自动保存
}
}
// 差异反转方法
private invertDiff(diff: DataDiff): DataDiff {
return {
added: diff.removed,
removed: diff.added,
updated: diff.updated.map(u => ({ before: u.after, after: u.before }))
}
}
// 计算当前状态与快照的差异
private calculateDiff() {
const currentMap = new Map(this.vdata.items.map(item => [item.id, item]))
const added: ItemJson[] = []
const removed: ItemJson[] = []
const updated: { before: ItemJson; after: ItemJson }[] = []
// 检查删除 & 更新
for (const [id, item] of this.lastStateDict.entries()) {
if (!currentMap.has(id)) {
removed.push(item)
} else if (!_.isEqual(item, currentMap.get(id))) {
updated.push({
before: item,
after: currentMap.get(id)
})
}
}
// 检查新增
for (const [id, item] of currentMap.entries()) {
if (!this.lastStateDict.has(id)) {
added.push(item)
}
}
this.changeTracker.added = added
this.changeTracker.removed = removed
this.changeTracker.updated = updated
}
/**
* 保存差异到历史记录
*/
private saveStep() {
const { added, removed, updated } = this.changeTracker
if (added.length === 0 && removed.length === 0 && updated.length === 0) return
// 深拷贝差异对象
const clonedDiff = {
added: _.cloneDeep(added),
removed: _.cloneDeep(removed),
updated: _.cloneDeep(updated)
}
const step: HistoryStep = {
diff: clonedDiff,
timestamp: Date.now()
}
if (this.historySteps.length >= this.maxHistorySteps) {
this.historySteps.shift()
}
this.historySteps.push(step)
this.historyIndex = this.historySteps.length - 1
this.pendingChanges = true
}
/**
* 将当前数据 与 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)
this.entityManager.beginEntityUpdate()
for (const item of diff.added) {
this.entityManager.createOrUpdateEntity(item)
}
for (const { after } of diff.updated) {
this.entityManager.createOrUpdateEntity(after)
}
// 最后处理删除
for (const item of diff.removed) {
this.entityManager.deleteEntity(item.id)
}
this.entityManager.endEntityUpdate()
// 从实体管理器中获取最新数据
const updatedItems = this.entityManager.getAllEntities()
this.vdata.items = Array.from(updatedItems.values())
}
/**
* 撤销
*/
undo() {
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('撤销完成')
this.startAutoSave()
}
/**
* 重做
*/
redo() {
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('重做完成')
this.startAutoSave()
}
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()
}, 5000)
}
/**
* 从外部加载数据
*/
async load(data: Partial<VData>) {
this.isLoading.value = true
this.historySteps = Array.from({ length: this.maxHistorySteps }, () => null)
this.historyIndex = -1
try {
// 停止自动保存,避免在加载过程中触发
this.stopAutoSave()
// 直接替换数组引用(避免响应式开销)
//@ts-ignore
this.vdata = {
id: this.id,
isChanged: false,
...data
}
// 同步到视口
this.fullSync()
this.isChanged.value = false
// 强制保存一次初始状态
await this.saveToLocalstore()
console.log('[StateManager] 加载完成,共', data.items.length, '个对象')
} finally {
this.isLoading.value = false
}
}
// 差异应用方法
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 fullSync() {
this.entityManager.beginEntityUpdate()
this.vdata.items.forEach(item => {
this.entityManager.createOrUpdateEntity(item)
})
this.entityManager.endEntityUpdate()
// 初始状态作为第一步
this.beginStateUpdate()
this.endStateUpdate({ autoSave: false })
this.pendingChanges = false
}
undoEnabled() {
return this.historyIndex >= 0
}
redoEnabled() {
const nextIndex = (this.historyIndex + 1) % this.maxHistorySteps
return !!this.historySteps[nextIndex]
}
/**
* 保存数据到外部
*/
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() // 同步到视口
console.log('[StateManager] 从本地存储恢复', this.vdata.items.length, '个对象')
return true
}
return false
} catch (error) {
console.error('[StateManager] 从本地存储加载失败:', error)
return false
} finally {
this.isLoading.value = false
}
}
/**
* 删除本地存储
*/
async removeLocalstore() {
try {
await localforage.removeItem(this.storeKey)
console.log('[StateManager] 本地存储已清除')
} catch (error) {
console.error('[StateManager] 清除本地存储失败:', error)
}
}
/**
* 销毁资源
*/
destroy() {
// 清理引用
delete this.vdata
delete this.historySteps
}
/**
* 尝试从草稿中读取数据
*/
static async tryLoadCatalogFromLocalstore(): Promise<{
success: boolean,
catalogCode?: string,
catalog?: Catalog,
stateManagerId?: string
}> {
// 获取 url 中的 stateManagerId
// 从 localforage 中读取草稿数据
const stateManagerId = getQueryParams()?.get('store')
if (stateManagerId) {
const storeKey = `-tmp-yvan-lcc-${stateManagerId}`
const saved: VData = await localforage.getItem(storeKey)
if (saved && saved.catalog) {
const catalogCode = saved.catalogCode || ''
if (catalogCode) {
return {
success: true,
catalogCode,
catalog: saved.catalog,
stateManagerId
}
}
}
}
return {
success: false
}
}
}