template/packages-user/data-base/src/map/mapStore.ts

473 lines
15 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { logger } from '@motajs/common';
import { SaveCompression } from '../common';
import {
ILayerState,
ILayerStateSave,
IMapLayer,
IMapLayerSave,
IMapStore,
IMapStoreSave,
MapArea
} from './types';
import { LayerState } from './layerState';
import { uniq } from 'lodash-es';
export class MapStore implements IMapStore {
/** 楼层 id 到状态对象的映射 */
private readonly mapData: Map<string, LayerState> = new Map();
/** 所有楼层 id 的有序数组 */
readonly maps: string[] = [];
/** 差分压缩参考基准,首次 compareWith 后设置,之后不再更新 */
private refData: Map<string, Map<number, Uint32Array>> | null = null;
/** 分区列表 */
private areaList: Set<MapArea> = new Set();
/** 上一次调用 notifyEnterFloor 传入的楼层 id */
private lastFloorId: string | null = null;
/** 自动分区激活器开关 */
private autoActivitorEnabled: boolean = false;
//#region 楼层管理
createLayerState(id: string, width: number, height: number): ILayerState {
if (this.mapData.has(id)) {
logger.warn(121, id);
} else {
this.maps.push(id);
}
const state = new LayerState(width, height);
// 若 refData 已存在,新楼层直接视为全脏
if (this.refData !== null) {
state.setDirty(true);
}
this.mapData.set(id, state);
return state;
}
setMapList(maps: string[]): void {
this.maps.length = 0;
this.maps.push(...uniq(maps));
}
useManualOrder(sort: (arr: string[]) => string[]): void {
const copy = this.maps.slice();
const sorted = sort(copy);
const oldSet = new Set(this.maps);
const newSet = new Set(sorted);
if (oldSet.size !== newSet.size || !newSet.isSubsetOf(oldSet)) {
logger.warn(125);
return;
}
this.maps.length = 0;
this.maps.push(...uniq(sorted));
}
getLayerState(id: string): ILayerState | null {
return this.mapData.get(id) ?? null;
}
getActiveMap(id: string): ILayerState | null {
const state = this.mapData.get(id);
if (!state || !state.active) return null;
return state;
}
//#endregion
//#region 分区管理
setArea(areas: Set<MapArea>): void {
this.areaList = areas;
}
activeArea(id: string): void {
const idx = this.maps.indexOf(id);
if (idx === -1) return;
const area = this.findAreaByIndex(idx);
if (!area) return;
this.setAreaActive(area, true);
}
deactiveArea(id: string): void {
const idx = this.maps.indexOf(id);
if (idx === -1) return;
const area = this.findAreaByIndex(idx);
if (!area) return;
this.setAreaActive(area, false);
}
useAutoActivitor(enable: boolean): void {
this.autoActivitorEnabled = enable;
}
notifyEnterFloor(id: string): void {
if (!this.autoActivitorEnabled) return;
const idx = this.maps.indexOf(id);
if (idx === -1) return;
const area = this.findAreaByIndex(idx);
if (!area) return;
if (this.lastFloorId !== null) {
this.deactiveArea(this.lastFloorId);
}
this.activeArea(id);
this.lastFloorId = id;
}
private findAreaByIndex(idx: number): MapArea | null {
for (const area of this.areaList) {
for (const interval of area) {
if (idx >= interval.start && idx <= interval.end) {
return area;
}
}
}
return null;
}
private setAreaActive(area: MapArea, active: boolean): void {
for (const interval of area) {
for (let i = interval.start; i <= interval.end; i++) {
const floorId = this.maps[i];
if (floorId !== undefined) {
this.setMapActiveStatus(floorId, active);
}
}
}
}
//#endregion
//#region active 管理
isMapActive(id: string): boolean {
return this.mapData.get(id)?.active ?? false;
}
setMapActiveStatus(id: string, active: boolean): void {
this.mapData.get(id)?.setActiveStatus(active);
}
*iterateActiveMaps(): Iterable<[string, ILayerState]> {
for (const [id, state] of this.mapData) {
if (state.active) yield [id, state];
}
}
*iterateInactiveMaps(): Iterable<[string, ILayerState]> {
for (const [id, state] of this.mapData) {
if (!state.active) yield [id, state];
}
}
iterateAllMaps(): Iterable<[string, ILayerState]> {
return this.mapData;
}
//#endregion
//#region 存档及压缩
compareWith(ref: Map<string, Map<number, Uint32Array>>): void {
if (this.refData !== null) return;
this.refData = ref;
for (const [id, state] of this.mapData) {
const refFloor = ref.get(id);
if (!refFloor) {
state.setDirty(true);
continue;
}
let dirty = false;
for (const layer of state.layerList) {
const refArray = refFloor.get(layer.zIndex);
if (!refArray) {
dirty = true;
break;
}
const cur = layer.getMapRef().array;
if (cur.length !== refArray.length) {
dirty = true;
break;
}
if (cur.some((v, i) => refArray[i] !== v)) {
dirty = true;
break;
}
}
state.setDirty(dirty);
}
}
private saveNoCompression(): IMapStoreSave {
const floors = new Map<string, ILayerStateSave>();
for (const [id, state] of this.mapData) {
if (!state.active) continue;
floors.set(id, this.saveLayerStateFull(state));
}
return { floors };
}
private saveLowCompression(): IMapStoreSave {
const floors = new Map<string, ILayerStateSave>();
for (const [id, state] of this.mapData) {
if (!state.active) continue;
// 非 dirty 或 dirty 但与参考基准完全一致 → 空 layers读档时从参考基准恢复
if (
!state.isDirty() ||
(this.refData && this.isStateEqualToRef(id, state))
) {
floors.set(id, {
background: state.getBackground(),
layers: new Map()
});
} else {
floors.set(id, this.saveLayerStateFull(state));
}
}
return { floors };
}
private saveHighCompression(): IMapStoreSave {
const floors = new Map<string, ILayerStateSave>();
for (const [id, state] of this.mapData) {
if (!state.active) continue;
if (!state.isDirty()) {
floors.set(id, {
background: state.getBackground(),
layers: new Map()
});
continue;
}
const refFloor = this.refData?.get(id);
const layersMap = new Map<number, IMapLayerSave>();
for (const layer of state.layerList) {
const refArray = refFloor?.get(layer.zIndex);
const rows = this.diffRows(layer, refArray);
if (rows.size === 0 && refArray) continue; // 与参考完全一致
layersMap.set(layer.zIndex, {
width: layer.width,
height: layer.height,
rows
});
}
floors.set(id, {
background: state.getBackground(),
layers: layersMap
});
}
return { floors };
}
/**
* NoCompression 读档:每个图层均有 fullMap直接转移所有权无需参考基准。
*/
private loadNoCompression(state: IMapStoreSave): void {
for (const [id, cur] of this.mapData) {
cur.setActiveStatus(state.floors.has(id));
}
for (const [id, layerStateSave] of state.floors) {
const cur = this.mapData.get(id);
if (!cur) {
logger.warn(122, id);
continue;
}
cur.setBackground(layerStateSave.background);
for (const layer of cur.layerList) {
const layerSave = layerStateSave.layers.get(layer.zIndex);
if (!layerSave?.fullMap) continue;
layer.setMapRef(new Uint32Array(layerSave.fullMap));
}
cur.setDirty(false);
}
}
/**
* LowCompression 读档:
* - layers 有数据dirty 楼层)→ fullMap 直接转移所有权
* - layers 为空(非 dirty 楼层)→ 从参考基准恢复
*/
private loadLowCompression(state: IMapStoreSave): void {
if (!this.refData) {
logger.error(55);
return;
}
for (const [id, cur] of this.mapData) {
cur.setActiveStatus(state.floors.has(id));
}
for (const [id, layerStateSave] of state.floors) {
const cur = this.mapData.get(id);
const refFloor = this.refData.get(id);
if (!cur) {
logger.warn(122, id);
continue;
}
if (!refFloor) {
logger.warn(124, id);
continue;
}
cur.setBackground(layerStateSave.background);
for (const layer of cur.layerList) {
const layerSave = layerStateSave.layers.get(layer.zIndex);
if (layerSave?.fullMap) {
layer.setMapRef(layerSave.fullMap);
} else {
const refArray = refFloor?.get(layer.zIndex);
if (!refArray) {
logger.warn(124, id);
return;
}
layer.setMapRef(new Uint32Array(refArray));
}
}
cur.setDirty(false);
}
}
/**
* HighCompression 读档:
* - layers 有数据dirty 楼层)→ 以参考基准为底,叠加差分行
* - layers 为空(非 dirty 楼层或图层无变化rows 缺失)→ 从参考基准恢复
*/
private loadHighCompression(state: IMapStoreSave): void {
if (!this.refData) {
logger.error(55);
return;
}
for (const [id, cur] of this.mapData) {
cur.setActiveStatus(state.floors.has(id));
}
for (const [id, layerStateSave] of state.floors) {
const cur = this.mapData.get(id);
const refFloor = this.refData.get(id);
if (!cur) {
logger.warn(122, id);
continue;
}
if (!refFloor) {
logger.warn(124, id);
continue;
}
cur.setBackground(layerStateSave.background);
let isMapDirty = true;
for (const layer of cur.layerList) {
const refArray = refFloor.get(layer.zIndex);
if (!refArray) {
logger.warn(124, id);
continue;
}
const layerSave = layerStateSave.layers.get(layer.zIndex);
if (!layerSave?.rows || layerSave.rows.size === 0) {
// 图层无变化或非 dirty 楼层,从参考基准恢复
layer.setMapRef(new Uint32Array(refArray));
} else {
// 以参考基准为底,叠加差分行
isMapDirty = false;
const size = layer.width * layer.height;
const buf = new Uint32Array(size);
if (refArray) buf.set(refArray.subarray(0, size));
for (const [rowIdx, rowData] of layerSave.rows) {
buf.set(
rowData.subarray(0, layer.width),
rowIdx * layer.width
);
}
layer.setMapRef(buf);
}
}
cur.setDirty(isMapDirty);
}
}
saveState(compression: SaveCompression): IMapStoreSave {
if (compression === SaveCompression.HighCompression) {
return this.saveHighCompression();
} else if (compression === SaveCompression.LowCompression) {
return this.saveLowCompression();
} else {
return this.saveNoCompression();
}
}
loadState(state: IMapStoreSave, compression: SaveCompression): void {
if (compression === SaveCompression.HighCompression) {
this.loadHighCompression(state);
} else if (compression === SaveCompression.LowCompression) {
this.loadLowCompression(state);
} else {
this.loadNoCompression(state);
}
}
//#region 内部方法
/**
* 将楼层所有图层全量序列化NoCompression / LowCompression 用)
*/
private saveLayerStateFull(state: LayerState): ILayerStateSave {
const layersMap = new Map<number, IMapLayerSave>();
for (const layer of state.layerList) {
const arr = layer.getMapRef().array;
layersMap.set(layer.zIndex, {
width: layer.width,
height: layer.height,
fullMap: new Uint32Array(arr)
});
}
return { background: state.getBackground(), layers: layersMap };
}
/**
* 仅返回与参考基准不同的行HighCompression 用)
*/
private diffRows(
layer: IMapLayer,
refArray?: Uint32Array
): Map<number, Uint32Array> {
const rows = new Map<number, Uint32Array>();
const arr = layer.getMapRef().array;
if (refArray) {
for (let row = 0; row < layer.height; row++) {
const start = row * layer.width;
const end = start + layer.width;
const slice = arr.subarray(start, end);
const refSlice = refArray.subarray(start, end);
const same = refSlice.every((v, i) => slice[i] === v);
if (!same) {
rows.set(row, new Uint32Array(slice));
}
}
} else {
for (let row = 0; row < layer.height; row++) {
const start = row * layer.width;
const end = start + layer.width;
rows.set(row, new Uint32Array(arr.subarray(start, end)));
}
}
return rows;
}
/**
* 判断楼层所有图层是否与参考基准完全一致LowCompression 去误判用)
*/
private isStateEqualToRef(id: string, state: LayerState): boolean {
const refFloor = this.refData?.get(id);
if (!refFloor) return false;
for (const layer of state.layerList) {
const refArray = refFloor.get(layer.zIndex);
if (!refArray) return false;
const cur = layer.getMapRef().array;
if (cur.length !== refArray.length) return false;
for (let i = 0; i < cur.length; i++) {
if (cur[i] !== refArray[i]) return false;
}
}
return true;
}
//#endregion
}