mirror of
https://github.com/motajs/template.git
synced 2026-10-14 00:30:18 +08:00
fix(01-10): restore tile defaults and stable event snapshots
- Rebuild raw tile event baselines on construction, set, load, and conversion. - Preserve coordinate-owned point events through dynamic movement and copy save Maps.
This commit is contained in:
parent
a35e1e116d
commit
bcfa1f9467
@ -42,6 +42,21 @@ export class DynamicTile
|
|||||||
} else {
|
} else {
|
||||||
this.tileRaw = data;
|
this.tileRaw = data;
|
||||||
}
|
}
|
||||||
|
this.restoreDefaultEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据当前图块原始数据恢复默认事件并建立干净基准
|
||||||
|
*/
|
||||||
|
private restoreDefaultEvents(): void {
|
||||||
|
this.tileEvent().clear();
|
||||||
|
const data = this.raw();
|
||||||
|
if (data) {
|
||||||
|
for (const [priority, id] of Object.entries(data.events)) {
|
||||||
|
this.tileEvent().set(Number(priority), id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.tileEvent().markPure();
|
||||||
}
|
}
|
||||||
|
|
||||||
num(): number {
|
num(): number {
|
||||||
@ -61,6 +76,7 @@ export class DynamicTile
|
|||||||
} else {
|
} else {
|
||||||
this.tileRaw = data;
|
this.tileRaw = data;
|
||||||
}
|
}
|
||||||
|
this.restoreDefaultEvents();
|
||||||
}
|
}
|
||||||
|
|
||||||
setPos(x: number, y: number): void {
|
setPos(x: number, y: number): void {
|
||||||
@ -98,26 +114,27 @@ export class DynamicTile
|
|||||||
}
|
}
|
||||||
|
|
||||||
saveState(): Readonly<IDynamicBlockSave> {
|
saveState(): Readonly<IDynamicBlockSave> {
|
||||||
const save: IDynamicBlockSave = {
|
let save: IDynamicBlockSave;
|
||||||
num: this.num()
|
if (this.tileEvent().dirty()) {
|
||||||
};
|
save = {
|
||||||
|
num: this.num(),
|
||||||
if (this.triggers) {
|
events: new Map(this.tileEvent().get())
|
||||||
save.triggers = this.triggers;
|
};
|
||||||
|
} else {
|
||||||
|
save = {
|
||||||
|
num: this.num()
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return save;
|
return save;
|
||||||
}
|
}
|
||||||
|
|
||||||
loadState(save: Readonly<IDynamicBlockSave>): void {
|
loadState(save: Readonly<IDynamicBlockSave>): void {
|
||||||
if (save.triggers) {
|
this.restoreDefaultEvents();
|
||||||
if (save.triggers.size === 0) {
|
if (save.events) {
|
||||||
this.useEmptyTrigger();
|
this.tileEvent().clear();
|
||||||
} else {
|
for (const [priority, id] of save.events) {
|
||||||
save.triggers.forEach(v => this.addTrigger(v));
|
this.tileEvent().set(priority, id);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
this.clearTrigger();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -50,6 +50,11 @@ export class MapLayer
|
|||||||
/** 点事件视图,外层 key = y,内层 key = x */
|
/** 点事件视图,外层 key = y,内层 key = x */
|
||||||
private readonly pointEvents: Map<number, Map<number, ILayerEventView>> =
|
private readonly pointEvents: Map<number, Map<number, ILayerEventView>> =
|
||||||
new Map();
|
new Map();
|
||||||
|
/** 点事件的原始纯基准,外层 key = y,内层 key = x */
|
||||||
|
private readonly pointEventBaselines: Map<
|
||||||
|
number,
|
||||||
|
Map<number, ReadonlyMap<number, string>>
|
||||||
|
> = new Map();
|
||||||
/** 动态图块到其当前坐标的映射 */
|
/** 动态图块到其当前坐标的映射 */
|
||||||
private readonly posTileMap: Map<IDynamicTile, ITileLocator> = new Map();
|
private readonly posTileMap: Map<IDynamicTile, ITileLocator> = new Map();
|
||||||
/** 图层脏标记 */
|
/** 图层脏标记 */
|
||||||
@ -131,14 +136,120 @@ export class MapLayer
|
|||||||
private syncStaticEvent(tile: IDynamicTile, keepEvent: boolean): void {
|
private syncStaticEvent(tile: IDynamicTile, keepEvent: boolean): void {
|
||||||
const staticTile = this.getTile(tile.x, tile.y);
|
const staticTile = this.getTile(tile.x, tile.y);
|
||||||
if (!staticTile) return;
|
if (!staticTile) return;
|
||||||
staticTile.tileEvent().clear();
|
staticTile.set(staticTile.num());
|
||||||
if (keepEvent) {
|
if (keepEvent) {
|
||||||
|
staticTile.tileEvent().clear();
|
||||||
for (const [priority, id] of tile.tileEvent().get()) {
|
for (const [priority, id] of tile.tileEvent().get()) {
|
||||||
staticTile.tileEvent().set(priority, id);
|
staticTile.tileEvent().set(priority, id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 记录尚未保存过的点事件纯基准
|
||||||
|
* @param x 点事件横坐标
|
||||||
|
* @param y 点事件纵坐标
|
||||||
|
* @param eventView 点事件视图
|
||||||
|
*/
|
||||||
|
private capturePointEventBaseline(
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
eventView: ILayerEventView
|
||||||
|
): void {
|
||||||
|
const yMap = this.pointEventBaselines.get(y);
|
||||||
|
if (yMap?.has(x)) return;
|
||||||
|
if (eventView.dirty()) return;
|
||||||
|
const target = yMap ?? new Map<number, ReadonlyMap<number, string>>();
|
||||||
|
target.set(x, new Map(eventView.get()));
|
||||||
|
if (!yMap) this.pointEventBaselines.set(y, target);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将所有已创建的点事件视图恢复到原始纯基准
|
||||||
|
*/
|
||||||
|
private resetPointEvents(): void {
|
||||||
|
for (const [y, xMap] of this.pointEvents) {
|
||||||
|
for (const [x, eventView] of xMap) {
|
||||||
|
this.capturePointEventBaseline(x, y, eventView);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const [y, xMap] of this.pointEvents) {
|
||||||
|
for (const [x, eventView] of xMap) {
|
||||||
|
eventView.clear();
|
||||||
|
const baseline = this.pointEventBaselines.get(y)?.get(x);
|
||||||
|
if (baseline) {
|
||||||
|
for (const [priority, id] of baseline) {
|
||||||
|
eventView.set(priority, id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
eventView.markPure();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 裁剪超出新图层范围的点事件及其纯基准
|
||||||
|
* @param width 新图层宽度
|
||||||
|
* @param height 新图层高度
|
||||||
|
*/
|
||||||
|
private cropPointEvents(width: number, height: number): void {
|
||||||
|
for (const [y, xMap] of this.pointEvents) {
|
||||||
|
if (y >= height) {
|
||||||
|
this.pointEvents.delete(y);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (const x of xMap.keys()) {
|
||||||
|
if (x >= width) xMap.delete(x);
|
||||||
|
}
|
||||||
|
if (xMap.size === 0) this.pointEvents.delete(y);
|
||||||
|
}
|
||||||
|
for (const [y, xMap] of this.pointEventBaselines) {
|
||||||
|
if (y >= height) {
|
||||||
|
this.pointEventBaselines.delete(y);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (const x of xMap.keys()) {
|
||||||
|
if (x >= width) xMap.delete(x);
|
||||||
|
}
|
||||||
|
if (xMap.size === 0) this.pointEventBaselines.delete(y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 收集需要保存的点事件并复制其内部 Map
|
||||||
|
*/
|
||||||
|
private savePointEvents(): Map<number, ReadonlyMap<number, string>> {
|
||||||
|
const pointEvents = new Map<number, ReadonlyMap<number, string>>();
|
||||||
|
for (const [y, xMap] of this.pointEvents) {
|
||||||
|
for (const [x, eventView] of xMap) {
|
||||||
|
if (!eventView.dirty()) continue;
|
||||||
|
const index = this.map.indexer.locToIndex(x, y);
|
||||||
|
pointEvents.set(index, new Map(eventView.get()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return pointEvents;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在图层基准上叠加点事件存档
|
||||||
|
* @param save 点事件存档,可省略
|
||||||
|
*/
|
||||||
|
private loadPointEvents(
|
||||||
|
save?: ReadonlyMap<number, ReadonlyMap<number, string>>
|
||||||
|
): void {
|
||||||
|
this.resetPointEvents();
|
||||||
|
if (!save) return;
|
||||||
|
for (const [index, events] of save) {
|
||||||
|
const { x, y } = this.map.indexer.indexToLocator(index);
|
||||||
|
const eventView = this.event(x, y);
|
||||||
|
if (!eventView) continue;
|
||||||
|
eventView.clear();
|
||||||
|
for (const [priority, id] of events) {
|
||||||
|
eventView.set(priority, id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//#region 静态图层操作
|
//#region 静态图层操作
|
||||||
|
|
||||||
inMap(x: number, y: number): boolean {
|
inMap(x: number, y: number): boolean {
|
||||||
@ -321,6 +432,7 @@ export class MapLayer
|
|||||||
const tile = new DynamicTile(num, x, y, this);
|
const tile = new DynamicTile(num, x, y, this);
|
||||||
const location = this.getLocationData(x, y);
|
const location = this.getLocationData(x, y);
|
||||||
if (location) {
|
if (location) {
|
||||||
|
tile.tileEvent().clear();
|
||||||
for (const [priority, id] of location.static.tileEvent().get()) {
|
for (const [priority, id] of location.static.tileEvent().get()) {
|
||||||
tile.tileEvent().set(priority, id);
|
tile.tileEvent().set(priority, id);
|
||||||
}
|
}
|
||||||
@ -479,6 +591,8 @@ export class MapLayer
|
|||||||
if (!eventView) {
|
if (!eventView) {
|
||||||
eventView = new LayerEventView();
|
eventView = new LayerEventView();
|
||||||
xMap.set(x, eventView);
|
xMap.set(x, eventView);
|
||||||
|
} else {
|
||||||
|
this.capturePointEventBaseline(x, y, eventView);
|
||||||
}
|
}
|
||||||
return eventView;
|
return eventView;
|
||||||
}
|
}
|
||||||
@ -501,7 +615,13 @@ export class MapLayer
|
|||||||
}
|
}
|
||||||
|
|
||||||
dirty(): boolean {
|
dirty(): boolean {
|
||||||
return this.layerDirty;
|
if (this.layerDirty) return true;
|
||||||
|
for (const xMap of this.pointEvents.values()) {
|
||||||
|
for (const eventView of xMap.values()) {
|
||||||
|
if (eventView.dirty()) return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
markDirty(dirty: boolean): void {
|
markDirty(dirty: boolean): void {
|
||||||
@ -535,6 +655,7 @@ export class MapLayer
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.layerDirty = true;
|
this.layerDirty = true;
|
||||||
|
this.cropPointEvents(width, height);
|
||||||
this.mapData.expired = true;
|
this.mapData.expired = true;
|
||||||
const before = this.mapArray;
|
const before = this.mapArray;
|
||||||
const beforeWidth = this.width;
|
const beforeWidth = this.width;
|
||||||
@ -571,6 +692,8 @@ export class MapLayer
|
|||||||
|
|
||||||
resize2(width: number, height: number): void {
|
resize2(width: number, height: number): void {
|
||||||
this.layerDirty = true;
|
this.layerDirty = true;
|
||||||
|
this.pointEvents.clear();
|
||||||
|
this.pointEventBaselines.clear();
|
||||||
if (this.width === width && this.height === height) {
|
if (this.width === width && this.height === height) {
|
||||||
this.empty = true;
|
this.empty = true;
|
||||||
this.mapArray.fill(0);
|
this.mapArray.fill(0);
|
||||||
@ -655,7 +778,8 @@ export class MapLayer
|
|||||||
height: this.height,
|
height: this.height,
|
||||||
fullMap: new Uint32Array(this.mapArray),
|
fullMap: new Uint32Array(this.mapArray),
|
||||||
staticBlocks: this.saveStatics(),
|
staticBlocks: this.saveStatics(),
|
||||||
dynamicBlocks: this.saveDynamics()
|
dynamicBlocks: this.saveDynamics(),
|
||||||
|
pointEvents: this.savePointEvents()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -671,14 +795,16 @@ export class MapLayer
|
|||||||
height: this.height,
|
height: this.height,
|
||||||
fullMap: new Uint32Array(this.mapArray),
|
fullMap: new Uint32Array(this.mapArray),
|
||||||
staticBlocks,
|
staticBlocks,
|
||||||
dynamicBlocks
|
dynamicBlocks,
|
||||||
|
pointEvents: this.savePointEvents()
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
width: this.width,
|
width: this.width,
|
||||||
height: this.height,
|
height: this.height,
|
||||||
staticBlocks,
|
staticBlocks,
|
||||||
dynamicBlocks
|
dynamicBlocks,
|
||||||
|
pointEvents: this.savePointEvents()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -696,7 +822,8 @@ export class MapLayer
|
|||||||
height: this.height,
|
height: this.height,
|
||||||
rows: this.diffRows(this.refArray),
|
rows: this.diffRows(this.refArray),
|
||||||
staticBlocks,
|
staticBlocks,
|
||||||
dynamicBlocks
|
dynamicBlocks,
|
||||||
|
pointEvents: this.savePointEvents()
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
@ -704,7 +831,8 @@ export class MapLayer
|
|||||||
height: this.height,
|
height: this.height,
|
||||||
fullMap: new Uint32Array(this.mapArray),
|
fullMap: new Uint32Array(this.mapArray),
|
||||||
staticBlocks,
|
staticBlocks,
|
||||||
dynamicBlocks
|
dynamicBlocks,
|
||||||
|
pointEvents: this.savePointEvents()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -712,7 +840,8 @@ export class MapLayer
|
|||||||
width: this.width,
|
width: this.width,
|
||||||
height: this.height,
|
height: this.height,
|
||||||
staticBlocks,
|
staticBlocks,
|
||||||
dynamicBlocks
|
dynamicBlocks,
|
||||||
|
pointEvents: this.savePointEvents()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -833,6 +962,7 @@ export class MapLayer
|
|||||||
} else {
|
} else {
|
||||||
this.loadNoCompression(save);
|
this.loadNoCompression(save);
|
||||||
}
|
}
|
||||||
|
this.loadPointEvents(save.pointEvents);
|
||||||
}
|
}
|
||||||
|
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|||||||
228
packages-user/data-base/src/map/mapLifecycle.test.ts
Normal file
228
packages-user/data-base/src/map/mapLifecycle.test.ts
Normal file
@ -0,0 +1,228 @@
|
|||||||
|
import { beforeAll, describe, expect, it, vi } from 'vitest';
|
||||||
|
|
||||||
|
interface TestModules {
|
||||||
|
MapState: typeof import('./mapState').MapState;
|
||||||
|
TileStore: typeof import('@user/data-common').TileStore;
|
||||||
|
RoleFaceBinder: typeof import('@user/data-common').RoleFaceBinder;
|
||||||
|
FaceManager: typeof import('@user/data-common').FaceManager;
|
||||||
|
Dir8FaceHandler: typeof import('@user/data-common').Dir8FaceHandler;
|
||||||
|
SaveCompression: typeof import('@user/data-common').SaveCompression;
|
||||||
|
}
|
||||||
|
|
||||||
|
let modules: TestModules;
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
vi.stubGlobal('main', { replayChecking: true });
|
||||||
|
vi.stubGlobal('location', { origin: 'http://localhost' });
|
||||||
|
const mapModule = await import('./mapState');
|
||||||
|
const commonModule = await import('@user/data-common');
|
||||||
|
modules = {
|
||||||
|
MapState: mapModule.MapState,
|
||||||
|
TileStore: commonModule.TileStore,
|
||||||
|
RoleFaceBinder: commonModule.RoleFaceBinder,
|
||||||
|
FaceManager: commonModule.FaceManager,
|
||||||
|
Dir8FaceHandler: commonModule.Dir8FaceHandler,
|
||||||
|
SaveCompression: commonModule.SaveCompression
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
function createMapState(
|
||||||
|
pointEvents: Record<number, Record<number, string>> = {}
|
||||||
|
) {
|
||||||
|
const tileStore = new modules.TileStore();
|
||||||
|
tileStore.addTile({
|
||||||
|
num: 1,
|
||||||
|
id: 'base',
|
||||||
|
events: { 10: 'base-event' },
|
||||||
|
type: 0,
|
||||||
|
pass: { onlyEvents: false, outPass: 15, inPass: 15 },
|
||||||
|
eventPass: true
|
||||||
|
});
|
||||||
|
tileStore.addTile({
|
||||||
|
num: 2,
|
||||||
|
id: 'alternate',
|
||||||
|
events: { 20: 'alternate-event' },
|
||||||
|
type: 0,
|
||||||
|
pass: { onlyEvents: false, outPass: 15, inPass: 15 },
|
||||||
|
eventPass: true
|
||||||
|
});
|
||||||
|
const faceManager = new modules.FaceManager();
|
||||||
|
faceManager.register(1, new modules.Dir8FaceHandler());
|
||||||
|
const state = {
|
||||||
|
tileStore,
|
||||||
|
itemStore: {},
|
||||||
|
mapStore: {},
|
||||||
|
eventStore: {},
|
||||||
|
roleFace: new modules.RoleFaceBinder(),
|
||||||
|
faceManager,
|
||||||
|
saveSystem: {}
|
||||||
|
};
|
||||||
|
const mapState = new modules.MapState(tileStore, state);
|
||||||
|
const map = mapState.fromRaw({
|
||||||
|
floorId: 'F1',
|
||||||
|
width: 2,
|
||||||
|
map: { 0: [1, 1, 1, 1] },
|
||||||
|
layerAlias: { 0: 'event' },
|
||||||
|
events: { 0: pointEvents }
|
||||||
|
});
|
||||||
|
return { map: map!, layer: map!.getLayerByAlias('event')! };
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('MapLayer tile defaults snapshots conversion and movement', () => {
|
||||||
|
it('tile defaults snapshots conversion and movement', () => {
|
||||||
|
const { layer } = createMapState();
|
||||||
|
const staticTile = layer.getTile(0, 0)!;
|
||||||
|
expect(staticTile.tileEvent().get()).toEqual(
|
||||||
|
new Map([[10, 'base-event']])
|
||||||
|
);
|
||||||
|
expect(staticTile.tileEvent().dirty()).toBe(false);
|
||||||
|
|
||||||
|
staticTile.set(2);
|
||||||
|
expect(staticTile.tileEvent().get()).toEqual(
|
||||||
|
new Map([[20, 'alternate-event']])
|
||||||
|
);
|
||||||
|
expect(staticTile.tileEvent().dirty()).toBe(false);
|
||||||
|
|
||||||
|
const dynamicTile = layer.createDynamic(1, 1, 0);
|
||||||
|
expect(dynamicTile.tileEvent().get()).toEqual(
|
||||||
|
new Map([[10, 'base-event']])
|
||||||
|
);
|
||||||
|
dynamicTile.set(2);
|
||||||
|
expect(dynamicTile.tileEvent().get()).toEqual(
|
||||||
|
new Map([[20, 'alternate-event']])
|
||||||
|
);
|
||||||
|
expect(dynamicTile.tileEvent().dirty()).toBe(false);
|
||||||
|
|
||||||
|
const cleanSave = dynamicTile.saveState();
|
||||||
|
expect(cleanSave.events).toBeUndefined();
|
||||||
|
dynamicTile.loadState(cleanSave);
|
||||||
|
expect(dynamicTile.tileEvent().get()).toEqual(
|
||||||
|
new Map([[20, 'alternate-event']])
|
||||||
|
);
|
||||||
|
expect(dynamicTile.tileEvent().dirty()).toBe(false);
|
||||||
|
|
||||||
|
dynamicTile.tileEvent().set(30, 'override-event');
|
||||||
|
const overrideSave = dynamicTile.saveState();
|
||||||
|
const savedEvents = new Map(overrideSave.events!);
|
||||||
|
dynamicTile.tileEvent().set(30, 'changed-after-save');
|
||||||
|
expect(overrideSave.events).toEqual(savedEvents);
|
||||||
|
dynamicTile.loadState(overrideSave);
|
||||||
|
expect(dynamicTile.tileEvent().get()).toEqual(
|
||||||
|
new Map([
|
||||||
|
[20, 'alternate-event'],
|
||||||
|
[30, 'override-event']
|
||||||
|
])
|
||||||
|
);
|
||||||
|
expect(dynamicTile.tileEvent().dirty()).toBe(true);
|
||||||
|
|
||||||
|
staticTile.set(1);
|
||||||
|
const discardDynamic = layer.transferToDynamic(0, 0)!;
|
||||||
|
discardDynamic.tileEvent().set(30, 'discarded-event');
|
||||||
|
const restoredStatic = layer.transferToStatic(discardDynamic, false)!;
|
||||||
|
expect(restoredStatic.tileEvent().get()).toEqual(
|
||||||
|
new Map([[10, 'base-event']])
|
||||||
|
);
|
||||||
|
expect(restoredStatic.tileEvent().dirty()).toBe(false);
|
||||||
|
|
||||||
|
const keptDynamic = layer.transferToDynamic(1, 0)!;
|
||||||
|
keptDynamic.tileEvent().set(30, 'kept-event');
|
||||||
|
const keptStatic = layer.transferToStatic(keptDynamic, true)!;
|
||||||
|
expect(keptStatic.tileEvent().get()).toEqual(
|
||||||
|
new Map([
|
||||||
|
[10, 'base-event'],
|
||||||
|
[30, 'kept-event']
|
||||||
|
])
|
||||||
|
);
|
||||||
|
expect(keptStatic.tileEvent().dirty()).toBe(true);
|
||||||
|
|
||||||
|
const point = layer.event(0, 1)!;
|
||||||
|
point.set(5, 'point-event');
|
||||||
|
point.markPure();
|
||||||
|
layer.getPointEvent(0, 1);
|
||||||
|
const moving = layer.transferToDynamic(0, 1)!;
|
||||||
|
moving.setPos(1, 1);
|
||||||
|
expect(layer.getPointEvent(0, 1)).toEqual(
|
||||||
|
new Map([[5, 'point-event']])
|
||||||
|
);
|
||||||
|
expect(layer.getPointEvent(1, 1)).toEqual(new Map());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('MapLayer point event lifecycle', () => {
|
||||||
|
it('point event lifecycle', () => {
|
||||||
|
const { layer } = createMapState({ 1: { 5: 'raw-point' } });
|
||||||
|
const point = layer.event(1, 0)!;
|
||||||
|
expect(point.dirty()).toBe(false);
|
||||||
|
|
||||||
|
point.set(7, 'runtime-point');
|
||||||
|
expect(layer.dirty()).toBe(true);
|
||||||
|
point.delete(7);
|
||||||
|
expect(point.dirty()).toBe(false);
|
||||||
|
point.set(5, 'changed-point');
|
||||||
|
expect(point.dirty()).toBe(true);
|
||||||
|
point.set(5, 'raw-point');
|
||||||
|
expect(point.dirty()).toBe(false);
|
||||||
|
point.clear();
|
||||||
|
expect(point.dirty()).toBe(true);
|
||||||
|
point.set(5, 'raw-point');
|
||||||
|
expect(point.dirty()).toBe(false);
|
||||||
|
|
||||||
|
const compressionLevels = [
|
||||||
|
modules.SaveCompression.NoCompression,
|
||||||
|
modules.SaveCompression.LowCompression,
|
||||||
|
modules.SaveCompression.HighCompression
|
||||||
|
];
|
||||||
|
for (const compression of compressionLevels) {
|
||||||
|
point.set(7, 'saved-point');
|
||||||
|
const save = layer.saveState(compression);
|
||||||
|
expect(save.pointEvents?.get(1)).toEqual(
|
||||||
|
new Map([
|
||||||
|
[5, 'raw-point'],
|
||||||
|
[7, 'saved-point']
|
||||||
|
])
|
||||||
|
);
|
||||||
|
point.set(7, 'changed-after-save');
|
||||||
|
layer.loadState(save, compression);
|
||||||
|
expect(point.get()).toEqual(
|
||||||
|
new Map([
|
||||||
|
[5, 'raw-point'],
|
||||||
|
[7, 'saved-point']
|
||||||
|
])
|
||||||
|
);
|
||||||
|
expect(point.dirty()).toBe(true);
|
||||||
|
point.set(8, 'after-load');
|
||||||
|
expect(save.pointEvents?.get(1)).toEqual(
|
||||||
|
new Map([
|
||||||
|
[5, 'raw-point'],
|
||||||
|
[7, 'saved-point']
|
||||||
|
])
|
||||||
|
);
|
||||||
|
point.delete(8);
|
||||||
|
}
|
||||||
|
|
||||||
|
point.set(8, 'runtime-only');
|
||||||
|
layer.loadState(
|
||||||
|
{
|
||||||
|
width: 2,
|
||||||
|
height: 2,
|
||||||
|
fullMap: new Uint32Array([1, 1, 1, 1])
|
||||||
|
},
|
||||||
|
modules.SaveCompression.NoCompression
|
||||||
|
);
|
||||||
|
expect(point.get()).toEqual(new Map([[5, 'raw-point']]));
|
||||||
|
expect(point.dirty()).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('resize preserves in-range point events and resize2 clears them', () => {
|
||||||
|
const { layer } = createMapState({
|
||||||
|
0: { 1: 'top-left' },
|
||||||
|
3: { 1: 'bottom-right' }
|
||||||
|
});
|
||||||
|
layer.resize(1, 1);
|
||||||
|
expect(layer.getPointEvent(0, 0)).toEqual(new Map([[1, 'top-left']]));
|
||||||
|
expect(layer.getPointEvent(1, 1)).toBeNull();
|
||||||
|
|
||||||
|
layer.resize2(1, 1);
|
||||||
|
expect(layer.getPointEvent(0, 0)).toEqual(new Map());
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -13,6 +13,21 @@ export class StaticTile
|
|||||||
{
|
{
|
||||||
constructor(x: number, y: number, layer: IMapLayer) {
|
constructor(x: number, y: number, layer: IMapLayer) {
|
||||||
super(x, y, layer);
|
super(x, y, layer);
|
||||||
|
this.restoreDefaultEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据当前图块原始数据恢复默认事件并建立干净基准
|
||||||
|
*/
|
||||||
|
private restoreDefaultEvents(): void {
|
||||||
|
this.tileEvent().clear();
|
||||||
|
const data = this.raw();
|
||||||
|
if (data) {
|
||||||
|
for (const [priority, id] of Object.entries(data.events)) {
|
||||||
|
this.tileEvent().set(Number(priority), id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.tileEvent().markPure();
|
||||||
}
|
}
|
||||||
|
|
||||||
num(): number {
|
num(): number {
|
||||||
@ -25,6 +40,7 @@ export class StaticTile
|
|||||||
|
|
||||||
set(num: number): void {
|
set(num: number): void {
|
||||||
this.layer.setBlock(num, this.locator.x, this.locator.y);
|
this.layer.setBlock(num, this.locator.x, this.locator.y);
|
||||||
|
this.restoreDefaultEvents();
|
||||||
}
|
}
|
||||||
|
|
||||||
toDynamic(): IDynamicTile {
|
toDynamic(): IDynamicTile {
|
||||||
@ -32,28 +48,28 @@ export class StaticTile
|
|||||||
}
|
}
|
||||||
|
|
||||||
shouldSave(): boolean {
|
shouldSave(): boolean {
|
||||||
return !!this.triggers;
|
return this.tileEvent().dirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
saveState(): Readonly<IStaticBlockSave> {
|
saveState(): Readonly<IStaticBlockSave> {
|
||||||
const save: IStaticBlockSave = {};
|
let save: IStaticBlockSave;
|
||||||
|
if (this.tileEvent().dirty()) {
|
||||||
if (this.triggers) {
|
save = {
|
||||||
save.triggers = this.triggers;
|
events: new Map(this.tileEvent().get())
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
save = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
return save;
|
return save;
|
||||||
}
|
}
|
||||||
|
|
||||||
loadState(save: Readonly<IStaticBlockSave>): void {
|
loadState(save: Readonly<IStaticBlockSave>): void {
|
||||||
if (save.triggers) {
|
this.restoreDefaultEvents();
|
||||||
if (save.triggers.size === 0) {
|
if (save.events) {
|
||||||
this.useEmptyTrigger();
|
this.tileEvent().clear();
|
||||||
} else {
|
for (const [priority, id] of save.events) {
|
||||||
save.triggers.forEach(v => this.addTrigger(v));
|
this.tileEvent().set(priority, id);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
this.clearTrigger();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user