mirror of
https://github.com/motajs/template.git
synced 2026-09-13 10:18:50 +08:00
test(03-07): prove serialized event replay path
- Load the fixed replay event through the internal serialized boundary - Assert trigger, raw statements, coordinate binding, one execution, and final state
This commit is contained in:
parent
a111b8dcd4
commit
1b93deb8c8
66
packages-user/data-state/test/coreSerializedEvents.test.ts
Normal file
66
packages-user/data-state/test/coreSerializedEvents.test.ts
Normal file
@ -0,0 +1,66 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { EventTrigger, SaveCompression } from '@user/data-common';
|
||||
import { IBlockEventEnv, IBlockEventParam } from '@user/data-system';
|
||||
import { createClosedLoopFixture } from './fixtures/closed-loop';
|
||||
|
||||
describe('CoreState serialized event loading', () => {
|
||||
// 验证序列化事件保留触发器与原始语句,并经原始地图绑定坐标事件 id
|
||||
it('registers serialized event data before binding map event ids', () => {
|
||||
const fixture = createClosedLoopFixture();
|
||||
const event = fixture.state.eventStore.getEvent<
|
||||
IBlockEventParam,
|
||||
IBlockEventEnv,
|
||||
void
|
||||
>(fixture.eventId);
|
||||
|
||||
expect(event).not.toBeNull();
|
||||
if (!event) throw new Error('serialized event was not registered');
|
||||
expect(event.trigger).toBe(EventTrigger.OnEnter);
|
||||
expect(event.rawEvent).toBe(fixture.rawEvent);
|
||||
expect(fixture.eventLayer.getPointEvent(1, 0)).toEqual(
|
||||
new Map([[10, fixture.eventId]])
|
||||
);
|
||||
});
|
||||
|
||||
// 验证固定录像只执行一次已注册事件并得到稳定的最终数据快照
|
||||
it('executes the registered event once through the replay path', async () => {
|
||||
const fixture = createClosedLoopFixture();
|
||||
const event = fixture.state.eventStore.getEvent<
|
||||
IBlockEventParam,
|
||||
IBlockEventEnv,
|
||||
void
|
||||
>(fixture.eventId);
|
||||
|
||||
expect(event).not.toBeNull();
|
||||
if (!event) throw new Error('serialized event was not registered');
|
||||
const execute = event.execute.bind(event);
|
||||
let executionCount = 0;
|
||||
event.execute = async (param, env) => {
|
||||
executionCount++;
|
||||
await execute(param, env);
|
||||
};
|
||||
|
||||
fixture.sandbox.play();
|
||||
await waitForEnded(fixture.sandbox);
|
||||
|
||||
expect(executionCount).toBe(1);
|
||||
expect(fixture.eventCompleted()).toBe(true);
|
||||
expect(
|
||||
fixture.state.hero.saveState(SaveCompression.NoCompression).attribute
|
||||
).toEqual(
|
||||
fixture.expected.hero
|
||||
);
|
||||
expect(fixture.eventLayer.getMapData()).toEqual(
|
||||
fixture.expected.maps[0].layers[2].matrix
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
async function waitForEnded(sandbox: {
|
||||
readonly ended: boolean;
|
||||
}): Promise<void> {
|
||||
for (let index = 0; index < 1000 && !sandbox.ended; index++) {
|
||||
await new Promise<void>(resolve => setTimeout(resolve, 0));
|
||||
}
|
||||
if (!sandbox.ended) throw new Error('replay did not reach normal end');
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
import { AnonTokyoInterpreter } from 'anon-tokyo';
|
||||
import { Statement, StatementType } from 'anon-tokyo';
|
||||
import {
|
||||
EventTrigger,
|
||||
FaceDirection,
|
||||
@ -18,8 +18,11 @@ import {
|
||||
IMapLayer,
|
||||
IMapStoreSave
|
||||
} from '@user/data-base';
|
||||
import { IReadonlyBlockEvent } from '@user/data-system';
|
||||
import { CoreState, createCoreState } from '../../src/core.ts';
|
||||
import {
|
||||
ILegacySerializedLoadData,
|
||||
LOAD_SERIALIZED_DATA
|
||||
} from '../../src/legacy/dependencies.ts';
|
||||
import { ReplayCommandCode } from '../../src/replay/types.ts';
|
||||
|
||||
export interface IClosedLoopLayerSnapshot {
|
||||
@ -53,6 +56,8 @@ export interface IClosedLoopFixture {
|
||||
readonly sandbox: IReplaySandbox;
|
||||
readonly initialState: IClosedLoopInitialState;
|
||||
readonly expected: IClosedLoopExpectedSnapshot;
|
||||
readonly eventId: string;
|
||||
readonly rawEvent: Statement[];
|
||||
readonly reset: () => void;
|
||||
readonly eventCompleted: () => boolean;
|
||||
}
|
||||
@ -62,7 +67,7 @@ export function createClosedLoopFixture(): IClosedLoopFixture {
|
||||
state.tileStore.addTile({
|
||||
num: 1,
|
||||
id: 'floor',
|
||||
events: { 10: 'mutate-map' },
|
||||
events: {},
|
||||
type: TileType.Terrain,
|
||||
pass: { onlyEvents: false, outPass: 0b1111, inPass: 0b1111 },
|
||||
eventPass: true
|
||||
@ -76,87 +81,88 @@ export function createClosedLoopFixture(): IClosedLoopFixture {
|
||||
eventPass: true
|
||||
});
|
||||
|
||||
const map = state.maps.fromRaw({
|
||||
floorId: 'F1',
|
||||
width: 3,
|
||||
map: {
|
||||
0: [7, 7, 7],
|
||||
10: [8, 8, 8],
|
||||
20: [1, 1, 1],
|
||||
30: [9, 9, 9],
|
||||
40: [10, 10, 10]
|
||||
},
|
||||
layerAlias: {
|
||||
0: 'bg',
|
||||
10: 'bg2',
|
||||
20: 'event',
|
||||
30: 'fg',
|
||||
40: 'fg2'
|
||||
},
|
||||
events: {
|
||||
0: {},
|
||||
10: {},
|
||||
20: {},
|
||||
30: {},
|
||||
40: {}
|
||||
const rawEvent: Statement[] = [
|
||||
{
|
||||
type: StatementType.Call,
|
||||
functionName: 'eventSetBlock',
|
||||
builtIn: true,
|
||||
async: true,
|
||||
parameters: { x: 1, y: 0, tile: 2 }
|
||||
}
|
||||
});
|
||||
];
|
||||
const serialized: ILegacySerializedLoadData = {
|
||||
events: {
|
||||
'mutate-map': {
|
||||
trigger: EventTrigger.OnEnter,
|
||||
rawEvent
|
||||
}
|
||||
},
|
||||
maps: [
|
||||
{
|
||||
floorId: 'F1',
|
||||
width: 3,
|
||||
map: {
|
||||
0: [7, 7, 7],
|
||||
10: [8, 8, 8],
|
||||
20: [1, 1, 1],
|
||||
30: [9, 9, 9],
|
||||
40: [10, 10, 10]
|
||||
},
|
||||
layerAlias: {
|
||||
0: 'bg',
|
||||
10: 'bg2',
|
||||
20: 'event',
|
||||
30: 'fg',
|
||||
40: 'fg2'
|
||||
},
|
||||
events: {
|
||||
0: {},
|
||||
10: {},
|
||||
20: { 1: { 10: 'mutate-map' } },
|
||||
30: {},
|
||||
40: {}
|
||||
}
|
||||
},
|
||||
{
|
||||
floorId: 'F2',
|
||||
width: 2,
|
||||
map: {
|
||||
0: [11, 11, 11, 11],
|
||||
10: [12, 12, 12, 12],
|
||||
20: [13, 13, 13, 13],
|
||||
30: [14, 14, 14, 14],
|
||||
40: [15, 15, 15, 15]
|
||||
},
|
||||
layerAlias: {
|
||||
0: 'bg',
|
||||
10: 'bg2',
|
||||
20: 'event',
|
||||
30: 'fg',
|
||||
40: 'fg2'
|
||||
},
|
||||
events: {
|
||||
0: {},
|
||||
10: {},
|
||||
20: {},
|
||||
30: {},
|
||||
40: {}
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
state[LOAD_SERIALIZED_DATA](serialized);
|
||||
const map = state.maps.getMap('F1');
|
||||
if (!map || !map.eventLayer) {
|
||||
throw new Error('closed-loop fixture map was not created');
|
||||
}
|
||||
const eventLayer = map.eventLayer;
|
||||
const secondMap = state.maps.fromRaw({
|
||||
floorId: 'F2',
|
||||
width: 2,
|
||||
map: {
|
||||
0: [11, 11, 11, 11],
|
||||
10: [12, 12, 12, 12],
|
||||
20: [13, 13, 13, 13],
|
||||
30: [14, 14, 14, 14],
|
||||
40: [15, 15, 15, 15]
|
||||
},
|
||||
layerAlias: {
|
||||
0: 'bg',
|
||||
10: 'bg2',
|
||||
20: 'event',
|
||||
30: 'fg',
|
||||
40: 'fg2'
|
||||
},
|
||||
events: {
|
||||
0: {},
|
||||
10: {},
|
||||
20: {},
|
||||
30: {},
|
||||
40: {}
|
||||
}
|
||||
});
|
||||
const secondMap = state.maps.getMap('F2');
|
||||
if (!secondMap || !secondMap.eventLayer) {
|
||||
throw new Error('closed-loop fixture second map was not created');
|
||||
}
|
||||
const eventLayer = map.eventLayer;
|
||||
state.maps.setMapActiveStatus('F1', true);
|
||||
state.maps.setMapActiveStatus('F2', true);
|
||||
|
||||
let completed = false;
|
||||
const event: IReadonlyBlockEvent = {
|
||||
interpreter: new AnonTokyoInterpreter({
|
||||
builtInFunctions: [],
|
||||
globalFunctions: []
|
||||
}),
|
||||
trigger: EventTrigger.OnEnter,
|
||||
rawEvent: [],
|
||||
compiled: null,
|
||||
compile: () => null,
|
||||
execute: async (_param, env) => {
|
||||
await Promise.resolve();
|
||||
if (!env.layer || !env.triggerLocator) {
|
||||
throw new Error('closed-loop event lost its map source');
|
||||
}
|
||||
env.layer.setBlock(2, env.triggerLocator.x, env.triggerLocator.y);
|
||||
completed = true;
|
||||
}
|
||||
};
|
||||
state.eventStore.addEvent('mutate-map', event);
|
||||
|
||||
state.hero.location.setFloor('F1');
|
||||
state.hero.location.setPos(0, 0);
|
||||
state.hero.location.mover.setFaceDir(FaceDirection.Right);
|
||||
@ -237,7 +243,6 @@ export function createClosedLoopFixture(): IClosedLoopFixture {
|
||||
initialState.enemy,
|
||||
SaveCompression.NoCompression
|
||||
);
|
||||
completed = false;
|
||||
};
|
||||
const sandbox = replay.createReplaySandbox({
|
||||
route: replay.route,
|
||||
@ -254,6 +259,8 @@ export function createClosedLoopFixture(): IClosedLoopFixture {
|
||||
initialState,
|
||||
expected,
|
||||
reset,
|
||||
eventCompleted: () => completed
|
||||
eventId: 'mutate-map',
|
||||
rawEvent,
|
||||
eventCompleted: () => eventLayer.getBlock(1, 0) === 2
|
||||
};
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user