将游戏内 玩法介绍弹窗的释放做了优化,并且下载做了提前下载bundle,优先级放在了 GameScene 最低优先级。
This commit is contained in:
parent
ebe5a4ec35
commit
9ae6b0e6af
File diff suppressed because it is too large
Load Diff
|
|
@ -1213,6 +1213,7 @@ export default class MapConroler extends cc.Component {
|
|||
if (!this.isMapRuntimeAlive()) return;
|
||||
this.ensureWinLoaded(error => onComplete(error));
|
||||
} : null,
|
||||
() => this.SceneManager.scheduleGameplayIntroPredownload(),
|
||||
);
|
||||
}, 0);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -45,10 +45,17 @@ export default class SceneManager extends cc.Component {
|
|||
if (bundleName === "action_bundle") return;
|
||||
if (!bundle) return;
|
||||
setTimeout(() => {
|
||||
if (cc.fx.GameTool && cc.fx.GameTool.shouldRetainGameplayPopupBundlesForGameSceneReload
|
||||
if (bundleName !== "gameplay_intro" && cc.fx.GameTool && cc.fx.GameTool.shouldRetainGameplayPopupBundlesForGameSceneReload
|
||||
&& cc.fx.GameTool.shouldRetainGameplayPopupBundlesForGameSceneReload()) return;
|
||||
const active = SceneManager.activeInstance;
|
||||
if (active && cc.isValid(active, true)) return;
|
||||
if (active && cc.isValid(active, true)) {
|
||||
if (bundleName !== "gameplay_intro") return;
|
||||
// 旧场景晚到的介绍请求,仅在新场景确实接管此包时保留。
|
||||
if (Object.keys(active.dynamicPopupStates || {}).some(name => {
|
||||
const state = active.dynamicPopupStates[name];
|
||||
return state.bundle === bundle || (state.loading && state.bundleName === bundleName);
|
||||
})) return;
|
||||
}
|
||||
if (cc.assetManager.getBundle(bundleName) !== bundle) return;
|
||||
bundle.releaseAll();
|
||||
if (cc.assetManager.getBundle(bundleName) === bundle) {
|
||||
|
|
@ -210,6 +217,36 @@ export default class SceneManager extends cc.Component {
|
|||
private dynamicPopupRetries: { [popupName: string]: { displayName: string; retry: () => void } } = {};
|
||||
private dynamicPopupStates: { [popupName: string]: DynamicPopupState } = {};
|
||||
private popupBundleWarmupStarted: boolean = false;
|
||||
private gameplayIntroPredownloadScheduled: boolean = false;
|
||||
|
||||
/** 地图就绪且弹窗预热结束后调用;持续空闲一秒才派发,只下载,不 loadBundle。 */
|
||||
scheduleGameplayIntroPredownload() {
|
||||
//@ts-ignore
|
||||
if (cc.sys.platform !== cc.sys.WECHAT_GAME || typeof wx === "undefined" || !wx.preDownloadSubpackage) return;
|
||||
if (this.gameplayIntroPredownloadScheduled || !this.isPopupRuntimeAlive()) return;
|
||||
this.gameplayIntroPredownloadScheduled = true;
|
||||
let idle = false;
|
||||
const check = () => {
|
||||
if (!this.isPopupRuntimeAlive()) return;
|
||||
const tool = cc.fx.GameTool;
|
||||
if (tool.isGameplayIntroDownloaded()) return;
|
||||
const busy = tool.hasPendingGameplayResourceLoads() || cc.fx.GameConfig.isGameplayEntryPreparing()
|
||||
|| Object.keys(this.dynamicPopupLoads).length > 0
|
||||
|| Object.keys(this.dynamicPopupStates).some(name => this.dynamicPopupStates[name].loading);
|
||||
if (busy || !idle) {
|
||||
idle = !busy;
|
||||
this.scheduleOnce(check, 1);
|
||||
return;
|
||||
}
|
||||
tool.predownloadGameplayIntro().then(ready => {
|
||||
if (!ready && this.isPopupRuntimeAlive()) {
|
||||
idle = false;
|
||||
this.scheduleOnce(check, 10);
|
||||
}
|
||||
});
|
||||
};
|
||||
this.scheduleOnce(check, 1);
|
||||
}
|
||||
|
||||
warmupGameplayPopupBundles(
|
||||
preloadInstances: boolean = false,
|
||||
|
|
@ -297,11 +334,15 @@ export default class SceneManager extends cc.Component {
|
|||
&& !!cc.fx.GameTool
|
||||
&& !!cc.fx.GameTool.isIOSDevice
|
||||
&& cc.fx.GameTool.isIOSDevice();
|
||||
if (!shouldWarmupPauseBundle) return;
|
||||
if (!shouldWarmupPauseBundle) {
|
||||
if (onComplete) onComplete();
|
||||
return;
|
||||
}
|
||||
|
||||
const loadedPauseBundle = cc.assetManager.getBundle("pause");
|
||||
if (loadedPauseBundle) {
|
||||
console.log("[PopupBundle] already loaded: pause (iOS low-memory warmup)");
|
||||
if (onComplete) onComplete();
|
||||
return;
|
||||
}
|
||||
cc.assetManager.loadBundle("pause", (error: Error, bundle: cc.AssetManager.Bundle) => {
|
||||
|
|
@ -311,9 +352,11 @@ export default class SceneManager extends cc.Component {
|
|||
}
|
||||
if (error) {
|
||||
cc.warn("[PopupBundle] loadBundle failed: pause (iOS low-memory warmup)", error.message || error);
|
||||
if (onComplete) onComplete(error);
|
||||
return;
|
||||
}
|
||||
console.log("[PopupBundle] loadBundle complete: pause (iOS low-memory warmup)");
|
||||
if (onComplete) onComplete();
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -550,7 +593,10 @@ export default class SceneManager extends cc.Component {
|
|||
state.root = null;
|
||||
const bundle = state.bundle || cc.assetManager.getBundle(state.bundleName);
|
||||
state.bundle = null;
|
||||
if (bundle && !retainBundle) this.releaseDynamicPopupBundle(state.bundleName, bundle);
|
||||
// 介绍只缓存到当前关,不能混入五个常用弹窗的切关保留策略。
|
||||
if (bundle && (!retainBundle || state.bundleName === "gameplay_intro")) {
|
||||
this.releaseDynamicPopupBundle(state.bundleName, bundle);
|
||||
}
|
||||
}
|
||||
this.finishDynamicPopupLoad(popupName);
|
||||
}
|
||||
|
|
@ -879,6 +925,8 @@ export default class SceneManager extends cc.Component {
|
|||
&& cc.fx.GameTool.shouldRetainGameplayPopupBundlesForGameSceneReload();
|
||||
Object.keys(this.dynamicPopupStates || {}).forEach((popupName) =>
|
||||
this.releaseDynamicPopup(popupName, true, retainPopupBundles));
|
||||
const introBundle = cc.assetManager.getBundle("gameplay_intro");
|
||||
if (introBundle) this.releaseDynamicPopupBundleIfUnused("gameplay_intro", introBundle);
|
||||
if (!retainPopupBundles) {
|
||||
["pause", "NewMode", "propWindow", "lose"].forEach((bundleName) => {
|
||||
const bundle = cc.assetManager.getBundle(bundleName);
|
||||
|
|
|
|||
|
|
@ -718,7 +718,7 @@ export class GameConfig {
|
|||
vibrateOpen: true, //震动
|
||||
coinnum: 0, //每局的金币数
|
||||
paid_user: false, //是否是付费用户
|
||||
version: "1.0.58", //版本号
|
||||
version: "1.0.59", //版本号
|
||||
shushu_DistinctId: "", //数数访客ID
|
||||
shushu_AccountId: "", //数数账号ID
|
||||
uid: "", //用户和后端唯一id
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ window.initMgr = function () {
|
|||
cc.fx.GameConfig = GameConfig;
|
||||
cc.fx.HttpUtil = HttpUtil;
|
||||
cc.fx.GameTool = GameTool;
|
||||
GameTool.trackResourceLoads();
|
||||
cc.fx.Notifications = Notifications;
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -65,6 +65,12 @@ export default class BlockAssetManager {
|
|||
private foregroundLoads = 0;
|
||||
private entryDownloadHolds = 0;
|
||||
|
||||
/** 包括直存下载、排队任务和整批后台缓存,供最低优先级分包让路。 */
|
||||
hasPendingLoads(): boolean {
|
||||
return this.foregroundLoads > 0 || this.entryDownloadHolds > 0 || this.backgroundRunning
|
||||
|| this.loading.size > 0 || this.fileDownloads.size > 0 || this.downloadQueue.length > 0;
|
||||
}
|
||||
|
||||
/** 只停止后台派发;已在途下载继续完成,进关所需文件仍可提优先级。 */
|
||||
holdBackgroundDownloads(): () => void {
|
||||
this.entryDownloadHolds++;
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import Freeze from "../../prop/Freeze";
|
|||
import SceneManager from "../../SceneManager";
|
||||
import { MiniGameSdk } from "../../Sdk/MiniGameSdk";
|
||||
import Utils from "../Pay/Utils";
|
||||
import BlockAssetManager from "./BlockAssetManager";
|
||||
import {
|
||||
starterPackReward, applyStarterPackStatus, starterPackRemaining, starterPackNow,
|
||||
starterPackDay, starterPackPopupKey, starterPackFailureKey, starterPackRecordResult
|
||||
|
|
@ -12,7 +13,7 @@ import {
|
|||
//@ts-ignore
|
||||
//最大工具类 各种公共方法,以及处理上传,获取后端接口数据
|
||||
var GameTool = {
|
||||
MAX_NORMAL_LEVEL: 1980,
|
||||
MAX_NORMAL_LEVEL: 1960,
|
||||
starterPackReactivationUid: "",
|
||||
starterPackQueuedReason: "",
|
||||
MEMORY_WARNING_EVENT: "game-memory-warning",
|
||||
|
|
@ -34,6 +35,73 @@ var GameTool = {
|
|||
_lowMemoryMode: false,
|
||||
_memoryWarningCount: 0,
|
||||
_gameplayPopupSubpackageDownloaded: {},
|
||||
_resourceLoadTrackingInstalled: false,
|
||||
_pendingResourceLoads: 0,
|
||||
_gameplayIntroDownload: null as Promise<boolean>,
|
||||
|
||||
/** 在启动入口安装;公开管线覆盖 load/preload/Bundle 及其排队、解析阶段。 */
|
||||
trackResourceLoads() {
|
||||
if (this._resourceLoadTrackingInstalled) return;
|
||||
this._resourceLoadTrackingInstalled = true;
|
||||
[cc.assetManager.pipeline, cc.assetManager.fetchPipeline].forEach(pipeline => {
|
||||
pipeline.insert((task, done) => {
|
||||
this._pendingResourceLoads++;
|
||||
const complete = task.onComplete;
|
||||
let finished = false;
|
||||
task.onComplete = (...args) => {
|
||||
if (!finished) {
|
||||
finished = true;
|
||||
this._pendingResourceLoads--;
|
||||
}
|
||||
if (complete) complete.apply(task, args);
|
||||
};
|
||||
task.output = task.input;
|
||||
done(null);
|
||||
}, 0);
|
||||
});
|
||||
},
|
||||
|
||||
hasPendingGameplayResourceLoads(): boolean {
|
||||
return !this._resourceLoadTrackingInstalled || this._pendingResourceLoads > 0
|
||||
|| !!this._gameplayPopupSubpackagePreloadPromise || !!this._gameplayPopupMemoryWarmupPromise
|
||||
|| BlockAssetManager.instance.hasPendingLoads();
|
||||
},
|
||||
|
||||
isGameplayIntroDownloaded(): boolean {
|
||||
return !!this._gameplayPopupSubpackageDownloaded.gameplay_intro
|
||||
|| !!cc.assetManager.getBundle("gameplay_intro");
|
||||
},
|
||||
|
||||
/** 仅预下载;跨启动的磁盘缓存由微信检查,不持久化可能失效的“已下载”标记。 */
|
||||
predownloadGameplayIntro(): Promise<boolean> {
|
||||
if (this.isGameplayIntroDownloaded()) return Promise.resolve(true);
|
||||
if (this._gameplayIntroDownload) return this._gameplayIntroDownload;
|
||||
// 老基础库不回退到 loadSubpackage,避免后台预下载顺带执行分包脚本。
|
||||
//@ts-ignore
|
||||
const wechat = typeof wx !== "undefined" ? wx : null;
|
||||
if (!wechat || !wechat.preDownloadSubpackage) return Promise.resolve(false);
|
||||
this._gameplayIntroDownload = new Promise<boolean>(resolve => {
|
||||
console.log("[GameplayIntro] idle subpackage predownload start");
|
||||
try {
|
||||
wechat.preDownloadSubpackage({
|
||||
name: "gameplay_intro",
|
||||
success: () => {
|
||||
this._gameplayPopupSubpackageDownloaded.gameplay_intro = true;
|
||||
console.log("[GameplayIntro] subpackage cached; Bundle remains unloaded");
|
||||
resolve(true);
|
||||
},
|
||||
fail: error => { cc.warn("[GameplayIntro] predownload failed", error); resolve(false); },
|
||||
});
|
||||
} catch (error) {
|
||||
cc.warn("[GameplayIntro] predownload failed", error);
|
||||
resolve(false);
|
||||
}
|
||||
}).then(ready => {
|
||||
this._gameplayIntroDownload = null;
|
||||
return ready;
|
||||
});
|
||||
return this._gameplayIntroDownload;
|
||||
},
|
||||
_gameplayPopupSubpackageRetryTimer: null,
|
||||
_gameplayPopupSubpackagePreloadPromise: null,
|
||||
_gameplayPopupMemoryWarmupPromise: null,
|
||||
|
|
|
|||
13
assets/gacha_bundle/img/cat.meta
Normal file
13
assets/gacha_bundle/img/cat.meta
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"ver": "1.1.3",
|
||||
"uuid": "c9754f56-ad49-4bcc-a712-8f8e9e59dbb0",
|
||||
"importer": "folder",
|
||||
"isBundle": false,
|
||||
"bundleName": "",
|
||||
"priority": 1,
|
||||
"compressionType": {},
|
||||
"optimizeHotUpdate": {},
|
||||
"inlineSpriteFrames": {},
|
||||
"isRemoteBundle": {},
|
||||
"subMetas": {}
|
||||
}
|
||||
13
assets/libs/dn-sdk-minigame.meta
Normal file
13
assets/libs/dn-sdk-minigame.meta
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"ver": "1.1.3",
|
||||
"uuid": "543de93e-fba1-410a-a3cf-8b39b912464a",
|
||||
"importer": "folder",
|
||||
"isBundle": false,
|
||||
"bundleName": "",
|
||||
"priority": 1,
|
||||
"compressionType": {},
|
||||
"optimizeHotUpdate": {},
|
||||
"inlineSpriteFrames": {},
|
||||
"isRemoteBundle": {},
|
||||
"subMetas": {}
|
||||
}
|
||||
13
assets/passCheck/script.meta
Normal file
13
assets/passCheck/script.meta
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"ver": "1.1.3",
|
||||
"uuid": "4a7bec4e-6a81-4d81-86d8-edf1656cf2cf",
|
||||
"importer": "folder",
|
||||
"isBundle": false,
|
||||
"bundleName": "",
|
||||
"priority": 1,
|
||||
"compressionType": {},
|
||||
"optimizeHotUpdate": {},
|
||||
"inlineSpriteFrames": {},
|
||||
"isRemoteBundle": {},
|
||||
"subMetas": {}
|
||||
}
|
||||
13
assets/pause/texture.meta
Normal file
13
assets/pause/texture.meta
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"ver": "1.1.3",
|
||||
"uuid": "ccd7701f-585a-411b-a8ef-a9f03f22189f",
|
||||
"importer": "folder",
|
||||
"isBundle": false,
|
||||
"bundleName": "",
|
||||
"priority": 1,
|
||||
"compressionType": {},
|
||||
"optimizeHotUpdate": {},
|
||||
"inlineSpriteFrames": {},
|
||||
"isRemoteBundle": {},
|
||||
"subMetas": {}
|
||||
}
|
||||
|
|
@ -202,6 +202,7 @@ function mapFixture() {
|
|||
const manager = Object.assign(new Manager(), {
|
||||
node: { getChildByName: () => ({}) }, isPopupRuntimeAlive: () => !state.destroyed,
|
||||
scheduleOnce: done => state.frames.push(done),
|
||||
scheduleGameplayIntroPredownload: () => state.events.push('intro-idle'),
|
||||
loadDynamicPopup(name, bundle, prefab, parent, done, showLoading) {
|
||||
assert.equal(showLoading, false); state.popupCallbacks.push({ name, done });
|
||||
},
|
||||
|
|
@ -299,7 +300,7 @@ test('memory downgrade before map readiness still reveals the map but skips Pref
|
|||
ready.wall(); assert.equal(state.events.includes('down'), false);
|
||||
ready.block(); state.frames.shift()();
|
||||
assert.equal(Config.firstGameplayEntryComplete, true);
|
||||
assert.equal(state.holds, 0); assert.equal(state.events.at(-1), 'down');
|
||||
assert.equal(state.holds, 0); assert.deepEqual(state.events.slice(-2), ['down', 'intro-idle']);
|
||||
assert.equal(state.popupCallbacks.length, 0);
|
||||
});
|
||||
|
||||
|
|
|
|||
196
tools/test-gameplay-intro-loading.cjs
Normal file
196
tools/test-gameplay-intro-loading.cjs
Normal file
|
|
@ -0,0 +1,196 @@
|
|||
// Run: node --test tools/test-gameplay-intro-loading.cjs
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const vm = require('node:vm');
|
||||
const ts = require('typescript');
|
||||
|
||||
function loadClass(file, names, globals) {
|
||||
const source = ts.createSourceFile(file, fs.readFileSync(file, 'utf8'), ts.ScriptTarget.Latest, true);
|
||||
const declaration = source.statements.find(ts.isClassDeclaration);
|
||||
const members = names.map(name => declaration.members.find(m => m.name && m.name.getText(source) === name).getText(source));
|
||||
return vm.runInNewContext(ts.transpileModule(`class ${declaration.name.text} { ${members.join('\n')} }\n${declaration.name.text}`, {
|
||||
compilerOptions: { target: ts.ScriptTarget.ES2017 },
|
||||
}).outputText, globals);
|
||||
}
|
||||
const flush = async () => { for (let i = 0; i < 5; i++) await Promise.resolve(); };
|
||||
|
||||
function fixture() {
|
||||
const state = { blocksBusy: false, entryBusy: false, destroyed: false, requests: [], schedules: [], timers: [], bundles: new Map(), released: [] };
|
||||
const pipeline = () => ({ pipes: [], insert(fn, index) { this.pipes.splice(index, 0, fn); } });
|
||||
const cc = {
|
||||
sys: { platform: 'wechat', WECHAT_GAME: 'wechat' }, fx: {}, warn() {},
|
||||
Node: { EventType: { TOUCH_END: 'touchend' } },
|
||||
isValid: node => !!node && !node.destroyed,
|
||||
assetManager: {
|
||||
pipeline: pipeline(), fetchPipeline: pipeline(),
|
||||
getBundle: name => state.bundles.get(name),
|
||||
removeBundle: bundle => state.bundles.delete(bundle.name),
|
||||
loadBundle: () => assert.fail('background predownload must not load a Bundle'),
|
||||
},
|
||||
};
|
||||
const wx = { preDownloadSubpackage: options => state.requests.push(options) };
|
||||
const BlockAssetManager = { instance: { hasPendingLoads: () => state.blocksBusy } };
|
||||
const mod = { exports: {} };
|
||||
vm.runInNewContext(ts.transpileModule(fs.readFileSync('assets/Script/module/Tool/GameTool.ts', 'utf8'), {
|
||||
compilerOptions: { target: ts.ScriptTarget.ES2017, module: ts.ModuleKind.CommonJS },
|
||||
}).outputText, {
|
||||
module: mod, exports: mod.exports, cc, wx, console: { log() {} },
|
||||
require: name => name.endsWith('/BlockAssetManager') ? { default: BlockAssetManager } : { default: {}, MiniGameSdk: {} },
|
||||
});
|
||||
const tool = cc.fx.GameTool = mod.exports.GameTool;
|
||||
tool.trackResourceLoads();
|
||||
cc.fx.GameConfig = { isGameplayEntryPreparing: () => state.entryBusy };
|
||||
const Manager = loadClass('assets/Script/SceneManager.ts', [
|
||||
'activeInstance', 'scheduleGameplayIntroPredownload', 'warmupGameplayPopupBundles',
|
||||
'releaseDynamicPopup', 'releaseDynamicPopupBundle', 'releaseDynamicPopupBundleIfUnused',
|
||||
'releaseOrphanedPopupBundle', 'onDestroy',
|
||||
], { cc, wx, console: { log() {} }, setTimeout: fn => state.timers.push(fn) });
|
||||
const manager = Object.assign(new Manager(), {
|
||||
node: { getChildByName: () => null, off() {} },
|
||||
dynamicPopupStates: {}, dynamicPopupLoads: {},
|
||||
isPopupRuntimeAlive: () => !state.destroyed,
|
||||
scheduleOnce: (fn, delay) => state.schedules.push({ fn, delay }),
|
||||
finishDynamicPopupLoad() {}, unbindLosePanel() {}, unbindPropWindowPanel() {},
|
||||
clearDynamicPopupRetry() {}, clearFlyCoins() {}, disposeJungleTreasure() {},
|
||||
});
|
||||
tool.shouldRetainGameplayPopupBundlesForGameSceneReload = () => true;
|
||||
Manager.activeInstance = manager;
|
||||
const tick = () => { assert.ok(state.schedules.length); state.schedules.shift().fn(); };
|
||||
const bundle = name => {
|
||||
const result = { name, releaseAll: () => state.released.push(name) };
|
||||
state.bundles.set(name, result); return result;
|
||||
};
|
||||
return { state, cc, wx, tool, manager, Manager, tick, bundle };
|
||||
}
|
||||
|
||||
test('resource tracking preserves pipeline input/results and drains success and failure exactly once', () => {
|
||||
const { cc, tool } = fixture();
|
||||
tool.trackResourceLoads();
|
||||
for (const pipeline of [cc.assetManager.pipeline, cc.assetManager.fetchPipeline]) {
|
||||
assert.equal(pipeline.pipes.length, 1, 'tracking is installed once');
|
||||
let callbackArgs;
|
||||
const task = { input: ['asset'], onComplete(...args) { callbackArgs = args; } };
|
||||
pipeline.pipes[0](task, () => assert.equal(task.output, task.input));
|
||||
assert.equal(tool.hasPendingGameplayResourceLoads(), true);
|
||||
const error = new Error('download failed');
|
||||
task.onComplete(error, 'result');
|
||||
assert.deepEqual(callbackArgs, [error, 'result']);
|
||||
assert.equal(tool.hasPendingGameplayResourceLoads(), false);
|
||||
}
|
||||
});
|
||||
|
||||
test('Block idle check includes queued, in-flight, decoding and whole background batches', () => {
|
||||
const Block = loadClass('assets/Script/module/Tool/BlockAssetManager.ts', ['hasPendingLoads'], {});
|
||||
const block = Object.assign(new Block(), {
|
||||
foregroundLoads: 0, entryDownloadHolds: 0, backgroundRunning: false,
|
||||
loading: new Map(), fileDownloads: new Map(), downloadQueue: [],
|
||||
});
|
||||
assert.equal(block.hasPendingLoads(), false);
|
||||
for (const key of ['foregroundLoads', 'entryDownloadHolds']) {
|
||||
block[key] = 1; assert.equal(block.hasPendingLoads(), true); block[key] = 0;
|
||||
}
|
||||
for (const key of ['loading', 'fileDownloads']) {
|
||||
block[key].set('pending', {}); assert.equal(block.hasPendingLoads(), true); block[key].clear();
|
||||
}
|
||||
block.backgroundRunning = true; assert.equal(block.hasPendingLoads(), true); block.backgroundRunning = false;
|
||||
block.downloadQueue.push({}); assert.equal(block.hasPendingLoads(), true);
|
||||
});
|
||||
|
||||
test('intro waits for block queue, engine loading, popup downloads, user popup and stable idle', async () => {
|
||||
const { state, cc, tool, manager, tick } = fixture();
|
||||
manager.scheduleGameplayIntroPredownload();
|
||||
manager.scheduleGameplayIntroPredownload();
|
||||
assert.equal(state.schedules.length, 1);
|
||||
state.blocksBusy = true; tick(); tick();
|
||||
state.blocksBusy = false;
|
||||
const task = { input: 'queued asset' };
|
||||
cc.assetManager.pipeline.pipes[0](task, () => {}); tick();
|
||||
task.onComplete(null, {});
|
||||
tool._gameplayPopupSubpackagePreloadPromise = {}; tick();
|
||||
tool._gameplayPopupSubpackagePreloadPromise = null;
|
||||
tool._gameplayPopupMemoryWarmupPromise = {}; tick();
|
||||
tool._gameplayPopupMemoryWarmupPromise = null;
|
||||
manager.dynamicPopupLoads.pause = true; tick();
|
||||
delete manager.dynamicPopupLoads.pause;
|
||||
state.entryBusy = true; tick(); state.entryBusy = false;
|
||||
tick(); assert.equal(state.requests.length, 0);
|
||||
tick(); assert.equal(state.requests.length, 1);
|
||||
assert.equal(state.requests[0].name, 'gameplay_intro');
|
||||
state.requests[0].success(); await flush();
|
||||
assert.equal(tool.isGameplayIntroDownloaded(), true);
|
||||
assert.equal(state.bundles.size, 0, 'disk caching does not register a Bundle');
|
||||
await tool.predownloadGameplayIntro();
|
||||
assert.equal(state.requests.length, 1, 'successful session cache avoids duplicate download checks');
|
||||
});
|
||||
|
||||
test('failure retries only after a delay and another idle check; leaving scene cancels pending work', async () => {
|
||||
const { state, manager, tick } = fixture();
|
||||
manager.scheduleGameplayIntroPredownload(); tick(); tick();
|
||||
state.requests[0].fail(new Error('offline')); await flush();
|
||||
assert.equal(state.schedules[0].delay, 10);
|
||||
state.blocksBusy = true; tick(); tick();
|
||||
assert.equal(state.requests.length, 1);
|
||||
state.blocksBusy = false; tick(); tick();
|
||||
assert.equal(state.requests.length, 2);
|
||||
state.destroyed = true; state.requests[1].fail(new Error('offline')); await flush();
|
||||
assert.equal(state.schedules.length, 0);
|
||||
});
|
||||
|
||||
test('departing before idle never starts a download; old SDK does not execute a subpackage', () => {
|
||||
const f = fixture(); f.manager.scheduleGameplayIntroPredownload();
|
||||
f.state.destroyed = true; f.tick(); assert.equal(f.state.requests.length, 0);
|
||||
const old = fixture(); delete old.wx.preDownloadSubpackage;
|
||||
old.wx.loadSubpackage = () => assert.fail('must not execute scripts as background predownload');
|
||||
old.manager.scheduleGameplayIntroPredownload(); assert.equal(old.state.schedules.length, 0);
|
||||
});
|
||||
|
||||
test('concurrent callers share one download, and cached Bundle needs no predownload', async () => {
|
||||
const { tool, state, bundle } = fixture();
|
||||
const first = tool.predownloadGameplayIntro();
|
||||
assert.equal(tool.predownloadGameplayIntro(), first);
|
||||
state.requests[0].success(); assert.equal(await first, true);
|
||||
const second = fixture(); second.bundle('gameplay_intro');
|
||||
assert.equal(await second.tool.predownloadGameplayIntro(), true);
|
||||
assert.equal(second.state.requests.length, 0);
|
||||
});
|
||||
|
||||
test('scene reload releases intro resources while retaining ordinary popup bundles', () => {
|
||||
const { state, manager, bundle } = fixture();
|
||||
for (const [name, bundleName] of [['gameplayIntro', 'gameplay_intro'], ['pause', 'pause']]) {
|
||||
const root = { active: true, removeFromParent() {}, destroy() { this.destroyed = true; } };
|
||||
manager.dynamicPopupStates[name] = { bundleName, bundle: bundle(bundleName), root, callbacks: [], requestId: 1 };
|
||||
}
|
||||
manager.onDestroy();
|
||||
assert.deepEqual(state.released, ['gameplay_intro']);
|
||||
assert.equal(state.bundles.has('pause'), true);
|
||||
assert.equal(state.bundles.has('gameplay_intro'), false);
|
||||
});
|
||||
|
||||
test('unowned intro bundle is cleaned even with retention enabled and no popup state', () => {
|
||||
const { state, manager, bundle } = fixture(); bundle('gameplay_intro'); manager.onDestroy();
|
||||
assert.deepEqual(state.released, ['gameplay_intro']);
|
||||
});
|
||||
|
||||
for (const owned of [false, true]) {
|
||||
test(`late old-scene intro callback respects actual new-scene ownership: ${owned}`, () => {
|
||||
const { state, manager, Manager, bundle } = fixture(); const intro = bundle('gameplay_intro');
|
||||
if (owned) manager.dynamicPopupStates.gameplayIntro = { bundleName: 'gameplay_intro', loading: true };
|
||||
Manager.releaseOrphanedPopupBundle('gameplay_intro', intro); state.timers.shift()();
|
||||
assert.equal(state.bundles.has('gameplay_intro'), owned);
|
||||
});
|
||||
}
|
||||
|
||||
for (const mode of ['skip', 'cached', 'load', 'failure']) {
|
||||
test(`low-memory warmup signals completion so idle download can start: ${mode}`, () => {
|
||||
const { cc, tool, manager, bundle } = fixture(); let calls = 0, done;
|
||||
tool.isIOSDevice = () => mode !== 'skip';
|
||||
if (mode === 'cached') bundle('pause');
|
||||
cc.assetManager.loadBundle = (name, callback) => { assert.equal(name, 'pause'); done = callback; };
|
||||
manager.warmupGameplayPopupBundles(false, null, error => {
|
||||
assert.equal(!!error, mode === 'failure'); calls++;
|
||||
});
|
||||
if (done) { assert.equal(calls, 0); done(mode === 'failure' ? new Error('offline') : null, {}); }
|
||||
assert.equal(calls, 1);
|
||||
});
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user