|
|
@ -4,9 +4,10 @@ import localforage from 'localforage' |
|
|
import type EntityManager from './EntityManager' |
|
|
import type EntityManager from './EntityManager' |
|
|
import { markRaw, reactive, ref } from 'vue' |
|
|
import { markRaw, reactive, ref } from 'vue' |
|
|
import type Viewport from '@/core/engine/Viewport.ts' |
|
|
import type Viewport from '@/core/engine/Viewport.ts' |
|
|
import { getQueryParams, setQueryParam } from '@/utils/webutils.ts' |
|
|
import { getFreezeDeep, getQueryParams, setQueryParam } from '@/utils/webutils.ts' |
|
|
import { ensureEntityRelationsConsistency } from '@/core/ModelUtils.ts' |
|
|
import { ensureEntityRelationsConsistency } from '@/core/ModelUtils.ts' |
|
|
import EventBus from '@/runtime/EventBus.ts' |
|
|
import EventBus from '@/runtime/EventBus.ts' |
|
|
|
|
|
import { Vector2 } from 'three/src/math/Vector2' |
|
|
|
|
|
|
|
|
// 差异类型定义
|
|
|
// 差异类型定义
|
|
|
interface DataDiff { |
|
|
interface DataDiff { |
|
|
@ -28,38 +29,20 @@ interface HistoryStep { |
|
|
* 主要功能包括: |
|
|
* 主要功能包括: |
|
|
* 1. 管理场景数据的读取、保存, 以及临时保存、临时读取等功能 |
|
|
* 1. 管理场景数据的读取、保存, 以及临时保存、临时读取等功能 |
|
|
* 2. 管理撤销、重做功能 |
|
|
* 2. 管理撤销、重做功能 |
|
|
* 3. 交互控制组件通过如下步骤修改数据 |
|
|
* 3. 交互控制组件通过 update() 方法修改数据 |
|
|
* - 1. 调用 beginStateUpdate 开始修改数据 |
|
|
* this.viewport.stateManager.update(({ getEntity, putEntity, deleteEntity, addEntity }) => { |
|
|
* - 2. 直接修改 vdata 数据 |
|
|
* const entity = getEntity(id) // 获取实体
|
|
|
* - 3. 调用 endStateUpdate 完成数据修改 |
|
|
* entity.abc = 123 |
|
|
|
|
|
* putEntity(entity) // 提交修改
|
|
|
|
|
|
* |
|
|
|
|
|
* deleteEntity(id) // 删除实体
|
|
|
|
|
|
* addEntity(newEntity) // 添加新实体
|
|
|
|
|
|
* }) |
|
|
* 4. 内部如果进行了撤销、还原等操作,会通过 syncDataState() 方法将 vdata 数据与 EntityManager 进行同步 |
|
|
* 4. 内部如果进行了撤销、还原等操作,会通过 syncDataState() 方法将 vdata 数据与 EntityManager 进行同步 |
|
|
* 5. 注意,如果正在读取中,需要设置 isLoading = true,外部需要等待加载完成后再进行操作 |
|
|
* 5. 注意,如果正在读取中,需要设置 isLoading = true,外部需要等待加载完成后再进行操作 |
|
|
* |
|
|
* |
|
|
* 主要难点: |
|
|
* 主要难点: |
|
|
* - 单张地图数据量可能超过 10000 个对象, 需要高效的管理数据状态 |
|
|
* - 单张地图数据量可能超过 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 { |
|
|
export default class StateManager { |
|
|
/** |
|
|
/** |
|
|
@ -87,7 +70,7 @@ export default class StateManager { |
|
|
/** |
|
|
/** |
|
|
* 当前场景数据 |
|
|
* 当前场景数据 |
|
|
*/ |
|
|
*/ |
|
|
vdata: VData |
|
|
private ___vdata: VData |
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
* 使用循环缓冲区存储历史记录 |
|
|
* 使用循环缓冲区存储历史记录 |
|
|
@ -110,11 +93,6 @@ export default class StateManager { |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
* 数据快照(用于差异计算) |
|
|
|
|
|
*/ |
|
|
|
|
|
private lastStateDict = new Map<string, ItemJson>() |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* @param id 唯一场景标识符, 用于做临时存储的 key |
|
|
* @param id 唯一场景标识符, 用于做临时存储的 key |
|
|
* @param viewport 视口对象, 用于获取、同步当前场景的状态 |
|
|
* @param viewport 视口对象, 用于获取、同步当前场景的状态 |
|
|
* @param maxHistorySteps 最大回撤步数 默认为 20 |
|
|
* @param maxHistorySteps 最大回撤步数 默认为 20 |
|
|
@ -125,40 +103,12 @@ export default class StateManager { |
|
|
this.entityManager = viewport.entityManager |
|
|
this.entityManager = viewport.entityManager |
|
|
this.maxHistorySteps = maxHistorySteps |
|
|
this.maxHistorySteps = maxHistorySteps |
|
|
|
|
|
|
|
|
// this.historySteps = Array.from({ length: this.maxHistorySteps }, () => null)
|
|
|
|
|
|
this.historySteps = [] |
|
|
this.historySteps = [] |
|
|
|
|
|
|
|
|
this.pendingChanges = false |
|
|
this.pendingChanges = false |
|
|
this.isAutoSavingPaused = false |
|
|
this.isAutoSavingPaused = false |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* 开始用户操作(创建数据快照) |
|
|
|
|
|
*/ |
|
|
|
|
|
beginStateUpdate(option: StateUpdateOption = {}): 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 { |
|
|
private invertDiff(diff: DataDiff): DataDiff { |
|
|
return { |
|
|
return { |
|
|
@ -168,67 +118,6 @@ export default class StateManager { |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 计算当前状态与快照的差异
|
|
|
|
|
|
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 进行同步, 对比出不同的部分,分别进行更新 |
|
|
* - 调用 entityManager.beginEntityUpdate() 开始更新 |
|
|
* - 调用 entityManager.beginEntityUpdate() 开始更新 |
|
|
@ -267,10 +156,10 @@ export default class StateManager { |
|
|
|
|
|
|
|
|
// 获取被改过的数据, 覆盖之前的数据
|
|
|
// 获取被改过的数据, 覆盖之前的数据
|
|
|
const writeBackMap = this.entityManager.cloneWriteBackEntities() |
|
|
const writeBackMap = this.entityManager.cloneWriteBackEntities() |
|
|
for (let i = 0; i < this.vdata.items.length; i++) { |
|
|
for (let i = 0; i < this.___vdata.items.length; i++) { |
|
|
const item = this.vdata.items[i] |
|
|
const item = this.___vdata.items[i] |
|
|
if (writeBackMap.has(item.id)) { |
|
|
if (writeBackMap.has(item.id)) { |
|
|
this.vdata.items[i] = writeBackMap.get(item.id) |
|
|
this.___vdata.items[i] = writeBackMap.get(item.id) |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
@ -346,50 +235,60 @@ export default class StateManager { |
|
|
this.startAutoSave() |
|
|
this.startAutoSave() |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
startAutoSave() { |
|
|
// 差异应用方法
|
|
|
this.isAutoSavingPaused = false |
|
|
private applyDiff(diff: DataDiff) { |
|
|
|
|
|
const { added, removed, updated } = diff |
|
|
|
|
|
|
|
|
if (this.autoSaveTimer !== null) { |
|
|
// 删除
|
|
|
window.clearTimeout(this.autoSaveTimer) |
|
|
const removedIds = new Set(removed.map(item => item.id)) |
|
|
this.autoSaveTimer = null |
|
|
_.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) |
|
|
|
|
|
|
|
|
// 设置新的定时器,在 5 秒后尝试保存
|
|
|
// 更新
|
|
|
this.queueAutoSave() |
|
|
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) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
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 |
|
|
* @private |
|
|
|
|
|
*/ |
|
|
|
|
|
private fullSync(isAutoSave = true) { |
|
|
|
|
|
// 修补关系数据, 确保 center / in / out 关系满足一致性
|
|
|
|
|
|
this.___vdata.items = ensureEntityRelationsConsistency(this.___vdata.items) |
|
|
|
|
|
|
|
|
this.autoSaveTimer = window.setTimeout(async () => { |
|
|
this.entityManager.beginEntityUpdate() |
|
|
this.autoSaveTimer = null |
|
|
this.___vdata.items.forEach(item => { |
|
|
|
|
|
this.entityManager.createOrUpdateEntity(item) |
|
|
|
|
|
}) |
|
|
|
|
|
this.entityManager.endEntityUpdate() |
|
|
|
|
|
|
|
|
if (this.isUpdating) { |
|
|
// 初始状态作为第一步
|
|
|
const checkInterval = setInterval(() => { |
|
|
this.pendingChanges = false |
|
|
if (!this.isUpdating) { |
|
|
|
|
|
clearInterval(checkInterval) |
|
|
if (isAutoSave) { |
|
|
if (!this.isAutoSavingPaused) { |
|
|
this.startAutoSave() |
|
|
this.queueAutoSave() |
|
|
|
|
|
} |
|
|
} |
|
|
} else { |
|
|
|
|
|
console.log('wait for entityManager to finish updating...') |
|
|
|
|
|
} |
|
|
} |
|
|
}, 200) |
|
|
|
|
|
return |
|
|
undoEnabled() { |
|
|
|
|
|
return this.historyIndex >= 0 |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
await this.saveToLocalstore() |
|
|
redoEnabled() { |
|
|
}, 2500) |
|
|
const nextIndex = (this.historyIndex + 1) % this.maxHistorySteps |
|
|
|
|
|
return !!this.historySteps[nextIndex] |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
@ -406,7 +305,7 @@ export default class StateManager { |
|
|
|
|
|
|
|
|
// 直接替换数组引用(避免响应式开销)
|
|
|
// 直接替换数组引用(避免响应式开销)
|
|
|
//@ts-ignore
|
|
|
//@ts-ignore
|
|
|
this.vdata = { |
|
|
this.___vdata = { |
|
|
id: this.id, |
|
|
id: this.id, |
|
|
isChanged: false, |
|
|
isChanged: false, |
|
|
...data |
|
|
...data |
|
|
@ -426,73 +325,66 @@ export default class StateManager { |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 差异应用方法
|
|
|
|
|
|
private applyDiff(diff: DataDiff) { |
|
|
|
|
|
const { added, removed, updated } = diff |
|
|
|
|
|
|
|
|
|
|
|
// 删除
|
|
|
startAutoSave() { |
|
|
const removedIds = new Set(removed.map(item => item.id)) |
|
|
this.isAutoSavingPaused = false |
|
|
_.remove(this.vdata.items, item => removedIds.has(item.id)) |
|
|
|
|
|
|
|
|
|
|
|
// 新增
|
|
|
if (this.autoSaveTimer !== null) { |
|
|
const addedIds = new Set(added.map(i => i.id)) |
|
|
window.clearTimeout(this.autoSaveTimer) |
|
|
_.remove(this.vdata.items, item => addedIds.has(item.id)) |
|
|
this.autoSaveTimer = null |
|
|
this.vdata.items.push(...added) |
|
|
} |
|
|
|
|
|
|
|
|
// 更新
|
|
|
|
|
|
const updateMap = new Map(updated.map(u => [u.after.id, u.after])) |
|
|
// 设置新的定时器,在 5 秒后尝试保存
|
|
|
for (const [key, after] of updateMap) { |
|
|
this.queueAutoSave() |
|
|
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) |
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
stopAutoSave() { |
|
|
|
|
|
if (this.autoSaveTimer !== null) { |
|
|
|
|
|
window.clearTimeout(this.autoSaveTimer) |
|
|
|
|
|
this.autoSaveTimer = null |
|
|
} |
|
|
} |
|
|
|
|
|
this.isAutoSavingPaused = true |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/** |
|
|
private queueAutoSave() { |
|
|
* 完整读取数据 |
|
|
if (this.autoSaveTimer !== null || this.isAutoSavingPaused) return |
|
|
* @private |
|
|
if (!this.pendingChanges) return |
|
|
*/ |
|
|
|
|
|
private fullSync() { |
|
|
|
|
|
// 修补关系数据, 确保 center / in / out 关系满足一致性
|
|
|
|
|
|
this.vdata.items = ensureEntityRelationsConsistency(this.vdata.items) |
|
|
|
|
|
|
|
|
|
|
|
this.entityManager.beginEntityUpdate() |
|
|
this.autoSaveTimer = window.setTimeout(async () => { |
|
|
this.vdata.items.forEach(item => { |
|
|
this.autoSaveTimer = null |
|
|
this.entityManager.createOrUpdateEntity(item) |
|
|
|
|
|
}) |
|
|
|
|
|
this.entityManager.endEntityUpdate() |
|
|
|
|
|
|
|
|
|
|
|
// 初始状态作为第一步
|
|
|
if (this.isUpdating) { |
|
|
this.beginStateUpdate() |
|
|
const checkInterval = setInterval(() => { |
|
|
this.endStateUpdate({ autoSave: false }) |
|
|
if (!this.isUpdating) { |
|
|
this.pendingChanges = false |
|
|
clearInterval(checkInterval) |
|
|
|
|
|
if (!this.isAutoSavingPaused) { |
|
|
|
|
|
this.queueAutoSave() |
|
|
} |
|
|
} |
|
|
|
|
|
} else { |
|
|
undoEnabled() { |
|
|
console.log('wait for entityManager to finish updating...') |
|
|
return this.historyIndex >= 0 |
|
|
} |
|
|
|
|
|
}, 200) |
|
|
|
|
|
return |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
redoEnabled() { |
|
|
await this.saveToLocalstore() |
|
|
const nextIndex = (this.historyIndex + 1) % this.maxHistorySteps |
|
|
}, 2000) |
|
|
return !!this.historySteps[nextIndex] |
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
* 保存数据到外部 |
|
|
* 保存数据到外部 |
|
|
*/ |
|
|
*/ |
|
|
async save(): Promise<Object> { |
|
|
async save(): Promise<Object> { |
|
|
return _.cloneDeep(this.vdata) |
|
|
return _.cloneDeep(this.___vdata) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
* 保存到本地存储 浏览器indexDb(防止数据丢失) |
|
|
* 保存到本地存储 浏览器indexDb(防止数据丢失) |
|
|
*/ |
|
|
*/ |
|
|
async saveToLocalstore() { |
|
|
async saveToLocalstore() { |
|
|
await localforage.setItem(this.storeKey, this.vdata) |
|
|
await localforage.setItem(this.storeKey, this.___vdata) |
|
|
console.log('[StateManager] 数据已保存草稿:' + this.storeKey + ', 共', this.vdata.items.length, '个对象') |
|
|
console.log('[StateManager] 数据已保存草稿:' + this.storeKey + ', 共', this.___vdata.items.length, '个对象') |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
@ -503,13 +395,13 @@ export default class StateManager { |
|
|
this.isLoading.value = true |
|
|
this.isLoading.value = true |
|
|
const saved: VData = await localforage.getItem(this.storeKey) |
|
|
const saved: VData = await localforage.getItem(this.storeKey) |
|
|
if (saved) { |
|
|
if (saved) { |
|
|
this.vdata = { |
|
|
this.___vdata = { |
|
|
...saved |
|
|
...saved |
|
|
} |
|
|
} |
|
|
this.isChanged.value = saved.isChanged || false |
|
|
this.isChanged.value = saved.isChanged || false |
|
|
|
|
|
|
|
|
this.fullSync() // 同步到视口
|
|
|
this.fullSync(false) // 同步到视口
|
|
|
console.log('[StateManager] 从本地存储恢复', this.vdata.items.length, '个对象') |
|
|
console.log('[StateManager] 从本地存储恢复', this.___vdata.items.length, '个对象') |
|
|
return true |
|
|
return true |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@ -541,7 +433,7 @@ export default class StateManager { |
|
|
*/ |
|
|
*/ |
|
|
dispose() { |
|
|
dispose() { |
|
|
// 清理引用
|
|
|
// 清理引用
|
|
|
delete this.vdata |
|
|
delete this.___vdata |
|
|
delete this.historySteps |
|
|
delete this.historySteps |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@ -582,23 +474,151 @@ export default class StateManager { |
|
|
* 根据 ID 查找实体 |
|
|
* 根据 ID 查找实体 |
|
|
*/ |
|
|
*/ |
|
|
findItemById(linkStartPointId: string): ItemJson | undefined { |
|
|
findItemById(linkStartPointId: string): ItemJson | undefined { |
|
|
return _.find(this.vdata.items, item => item.id === linkStartPointId) |
|
|
return getFreezeDeep(_.find(this.___vdata.items, item => item.id === linkStartPointId)) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
* 根据位置查找实体, 可带类型 |
|
|
* 根据位置查找实体, 可带类型 |
|
|
*/ |
|
|
*/ |
|
|
findItemByPosition(point: THREE.Vector3, itemTypeName?: string): ItemJson | undefined { |
|
|
findItemByPosition(point: THREE.Vector3, itemTypeName?: string): ItemJson | undefined { |
|
|
for (const item of this.vdata.items) { |
|
|
for (const item of this.___vdata.items) { |
|
|
if (item.tf?.[0]?.[0] === point.x && item.tf?.[0]?.[2] === point.z) { |
|
|
if (item.tf?.[0]?.[0] === point.x && item.tf?.[0]?.[2] === point.z) { |
|
|
if (!itemTypeName || item.t === itemTypeName) { |
|
|
if (!itemTypeName || item.t === itemTypeName) { |
|
|
return item |
|
|
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 { |
|
|
export interface StateUpdateOption { |
|
|
|
|
|
autoSave?: boolean |
|
|
createFromInteraction?: boolean |
|
|
createFromInteraction?: boolean |
|
|
} |
|
|
} |
|
|
|