perf: stream story assets by page
This commit is contained in:
@@ -59,6 +59,7 @@ try {
|
||||
const consoleMessages = [];
|
||||
const pageErrors = [];
|
||||
const requestFailures = [];
|
||||
const requestedResourceUrls = [];
|
||||
page.on('console', (message) => {
|
||||
consoleMessages.push({ type: message.type(), text: message.text() });
|
||||
});
|
||||
@@ -72,6 +73,9 @@ try {
|
||||
failure: request.failure()?.errorText ?? 'unknown'
|
||||
});
|
||||
});
|
||||
page.on('request', (request) => {
|
||||
requestedResourceUrls.push(request.url());
|
||||
});
|
||||
|
||||
await page.goto(targetUrl, { waitUntil: 'domcontentloaded' });
|
||||
await page.evaluate(() => window.localStorage.clear());
|
||||
@@ -88,6 +92,7 @@ try {
|
||||
const emptySave = await readCampaignSave(page);
|
||||
assert(!emptySave.current && !emptySave.slot1, `Expected first run without campaign save: ${JSON.stringify(emptySave)}`);
|
||||
|
||||
const freshStoryRequestStartIndex = requestedResourceUrls.length;
|
||||
await clickLegacyUi(page, 962, 240);
|
||||
await waitForStoryReady(page);
|
||||
await setDebugQueryParam(page, 'debugOrderCommandReady', '1');
|
||||
@@ -108,6 +113,7 @@ try {
|
||||
);
|
||||
await page.screenshot({ path: `${screenshotDir}/rc-new-game-story.png`, fullPage: true });
|
||||
await assertCanvasPainted(page, 'new game story');
|
||||
await assertFreshStoryAssetStreaming(page, requestedResourceUrls, freshStoryRequestStartIndex);
|
||||
const firstBattleTransition = await advanceUntilBattle(page, 'first-battle-zhuo-commandery', {
|
||||
startDeployment: false,
|
||||
captureLastStory: true
|
||||
@@ -7504,6 +7510,140 @@ async function waitForStoryReady(page) {
|
||||
}, undefined, { timeout: 90000 });
|
||||
}
|
||||
|
||||
async function assertFreshStoryAssetStreaming(page, requestedResourceUrls, requestStartIndex) {
|
||||
for (let sampleIndex = 0; sampleIndex < 8; sampleIndex += 1) {
|
||||
const probe = await readStoryAssetStreamingProbe(page);
|
||||
assertStoryAssetStreamingProbe(probe, 0, 'fresh story page 1');
|
||||
assert(
|
||||
storyStreamingIndexes(probe.story.assetStreaming).every((pageIndex) => pageIndex <= 1),
|
||||
`Expected fresh story loading to stay within pages 1-2 before input: ${JSON.stringify(probe)}`
|
||||
);
|
||||
await page.waitForTimeout(75);
|
||||
}
|
||||
|
||||
let probe = await advanceOneStoryPage(page, 1);
|
||||
assertStoryAssetStreamingProbe(probe, 1, 'fresh story page 2');
|
||||
probe = await advanceOneStoryPage(page, 2);
|
||||
assertStoryAssetStreamingProbe(probe, 2, 'fresh story page 3');
|
||||
|
||||
while ((probe.story?.cutsceneActors?.length ?? 0) === 0 && probe.story?.pageIndex < probe.story?.totalPages - 1) {
|
||||
const targetPageIndex = probe.story.pageIndex + 1;
|
||||
probe = await advanceOneStoryPage(page, targetPageIndex);
|
||||
assertStoryAssetStreamingProbe(probe, targetPageIndex, `fresh story page ${targetPageIndex + 1}`);
|
||||
}
|
||||
|
||||
assert(
|
||||
(probe.story?.cutsceneActors?.length ?? 0) > 0 && probe.expectedUnitBaseTextureKeys.length > 0,
|
||||
`Expected the fresh story regression to reach a cutscene that uses unit base sheets: ${JSON.stringify(probe)}`
|
||||
);
|
||||
assert(
|
||||
sameJsonValue(
|
||||
[...probe.story.assetStreaming.currentUnitBaseTexturesLoaded].sort(),
|
||||
[...probe.expectedUnitBaseTextureKeys].sort()
|
||||
),
|
||||
`Expected every current cutscene unit base sheet to be loaded: ${JSON.stringify(probe)}`
|
||||
);
|
||||
|
||||
const actionRequests = requestedResourceUrls
|
||||
.slice(requestStartIndex)
|
||||
.filter((url) => /\/unit-[^/?]+-actions(?:-[^/?]+)?\.png(?:[?#]|$)/i.test(decodeURIComponent(url)));
|
||||
assert(
|
||||
actionRequests.length === 0,
|
||||
`Expected story streaming not to request battle action sheets: ${JSON.stringify(actionRequests)}`
|
||||
);
|
||||
}
|
||||
|
||||
async function advanceOneStoryPage(page, expectedPageIndex) {
|
||||
const previousPageIndex = expectedPageIndex - 1;
|
||||
const before = await readStoryAssetStreamingProbe(page);
|
||||
assert(
|
||||
before.story?.pageIndex === previousPageIndex,
|
||||
`Expected story page ${previousPageIndex + 1} before advancing once: ${JSON.stringify(before)}`
|
||||
);
|
||||
|
||||
await page.keyboard.press('Space');
|
||||
await page.waitForFunction((targetPageIndex) => {
|
||||
const scene = window.__HEROS_GAME__?.scene.getScene('StoryScene');
|
||||
const story = scene?.getDebugState?.();
|
||||
return (
|
||||
story?.pageIndex === targetPageIndex &&
|
||||
story?.assetStreaming?.currentPageReady === true &&
|
||||
story?.backgroundReady === true &&
|
||||
story?.activeTexture === story?.background &&
|
||||
scene?.transitioning === false
|
||||
);
|
||||
}, expectedPageIndex, { timeout: 90000 });
|
||||
|
||||
const after = await readStoryAssetStreamingProbe(page);
|
||||
assert(
|
||||
after.story?.pageIndex === previousPageIndex + 1,
|
||||
`Expected exactly one story page advance without skips: ${JSON.stringify(after)}`
|
||||
);
|
||||
return after;
|
||||
}
|
||||
|
||||
async function readStoryAssetStreamingProbe(page) {
|
||||
return page.evaluate(() => {
|
||||
const scene = window.__HEROS_GAME__?.scene.getScene('StoryScene');
|
||||
const story = scene?.getDebugState?.() ?? null;
|
||||
const plan = story && scene?.storyPageAssetPlan?.(story.pageIndex);
|
||||
const textureKeys = scene?.textures?.list ? Object.keys(scene.textures.list) : [];
|
||||
return {
|
||||
viewport: {
|
||||
width: window.innerWidth,
|
||||
height: window.innerHeight,
|
||||
devicePixelRatio: window.devicePixelRatio
|
||||
},
|
||||
story,
|
||||
expectedUnitBaseTextureKeys: plan?.unitTextureKeys ?? [],
|
||||
loadedUnitActionTextureKeys: textureKeys.filter((key) => key.endsWith('-actions')),
|
||||
transitioning: scene?.transitioning ?? null
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function assertStoryAssetStreamingProbe(probe, expectedPageIndex, label) {
|
||||
const streaming = probe.story?.assetStreaming;
|
||||
assert(
|
||||
probe.viewport?.width === baselineViewport.width &&
|
||||
probe.viewport?.height === baselineViewport.height &&
|
||||
probe.viewport?.devicePixelRatio === desktopBrowserDeviceScaleFactor,
|
||||
`Expected ${label} at the explicit 1920x1080 DPR1 baseline: ${JSON.stringify(probe.viewport)}`
|
||||
);
|
||||
assert(
|
||||
probe.story?.ready === true &&
|
||||
probe.story.pageIndex === expectedPageIndex &&
|
||||
streaming?.currentPageReady === true &&
|
||||
probe.story.backgroundReady === true &&
|
||||
probe.story.activeTexture === probe.story.background,
|
||||
`Expected ${label} to use its loaded deterministic background: ${JSON.stringify(probe)}`
|
||||
);
|
||||
assert(
|
||||
Array.isArray(streaming?.failedAssetKeys) && streaming.failedAssetKeys.length === 0,
|
||||
`Expected ${label} without story asset failures: ${JSON.stringify(probe)}`
|
||||
);
|
||||
assert(
|
||||
storyStreamingIndexes(streaming).every((pageIndex) => pageIndex <= expectedPageIndex + 1),
|
||||
`Expected ${label} streaming not to run beyond the current/next page window: ${JSON.stringify(probe)}`
|
||||
);
|
||||
assert(
|
||||
probe.expectedUnitBaseTextureKeys.every((key) => streaming.currentUnitBaseTexturesLoaded.includes(key)),
|
||||
`Expected ${label} current unit base sheets to be ready: ${JSON.stringify(probe)}`
|
||||
);
|
||||
assert(
|
||||
streaming.currentUnitActionTexturesLoaded.length === 0 && probe.loadedUnitActionTextureKeys.length === 0,
|
||||
`Expected ${label} to keep battle action sheets unloaded: ${JSON.stringify(probe)}`
|
||||
);
|
||||
}
|
||||
|
||||
function storyStreamingIndexes(streaming) {
|
||||
return [
|
||||
...(streaming?.readyPageIndexes ?? []),
|
||||
...(streaming?.queuedPageIndexes ?? []),
|
||||
...(Number.isInteger(streaming?.loadingPageIndex) ? [streaming.loadingPageIndex] : [])
|
||||
];
|
||||
}
|
||||
|
||||
async function waitForStoryOrEnding(page) {
|
||||
await page.waitForFunction(() => {
|
||||
const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? [];
|
||||
|
||||
Reference in New Issue
Block a user