fix: 修复七日活动依赖加载与战令自动弹窗排队
This commit is contained in:
parent
4bf2c7596f
commit
2e0b55b9e7
|
|
@ -614,7 +614,7 @@ export default class JiaZai extends cc.Component {
|
|||
}
|
||||
|
||||
/** loadBundle 只注册资源包;解析 Prefab 前必须等实际声明的跨包依赖就绪。 */
|
||||
private loadHomeBundleWithDependencies(bundleName: string, priority: number,
|
||||
public loadHomeBundleWithDependencies(bundleName: string, priority: number,
|
||||
callback: (error: Error, bundle?: cc.AssetManager.Bundle) => void, visited: string[] = []): void {
|
||||
let settled = false;
|
||||
const finish = (error: Error, bundle?: cc.AssetManager.Bundle) => {
|
||||
|
|
@ -4603,9 +4603,23 @@ export default class JiaZai extends cc.Component {
|
|||
const current = pass && pass[2];
|
||||
if (current && Number(current.time) > Date.now() && passTier(current) === 30) return;
|
||||
if (cc.fx.StorageMessage.getStorage(cacheKey) === dateStr) return;
|
||||
this.openPassCheckIntro(() => {
|
||||
cc.fx.StorageMessage.setStorage(cacheKey, dateStr);
|
||||
info.popPassCheck = false;
|
||||
// 排队期间提前准备资源;只加载,不创建弹窗或记录已展示。
|
||||
const preparing = !JiaZai.cachedPassIntroPrefab || !cc.isValid(JiaZai.cachedPassIntroPrefab, true) || !this.isHomeBundleReady('passCheck')
|
||||
? this.loadHomePopupPrefab('passCheck', 'prefab/passCheckWindow', 'cachedPassIntroPrefab', 1)
|
||||
.then(() => null, error => error)
|
||||
: null;
|
||||
HomePopupQueue.get(this.node).enqueue("passCheckIntro", 60, done => {
|
||||
const show = (error?: Error) => {
|
||||
if (error) { cc.warn('[战令每日弹窗] 预加载失败', error); done(); return; }
|
||||
if (!this.isHomeRuntimeAlive() || cc.fx.GameConfig.isGameplayEntryPreparing()
|
||||
|| cc.fx.StorageMessage.getStorage(cacheKey) === dateStr) { done(); return; }
|
||||
this.openPassCheckIntro(() => {
|
||||
cc.fx.StorageMessage.setStorage(cacheKey, dateStr);
|
||||
info.popPassCheck = false;
|
||||
}, done);
|
||||
};
|
||||
if (preparing) preparing.then(show);
|
||||
else show();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
@ -4621,15 +4635,15 @@ export default class JiaZai extends cc.Component {
|
|||
this.openPassCheckContent(event, customEventData);
|
||||
}
|
||||
|
||||
private openPassCheckIntro(onOpened?: () => void) {
|
||||
if (this.passContentLoading || (this.passCheckNode && cc.isValid(this.passCheckNode))) return;
|
||||
if (this.passIntroNode && cc.isValid(this.passIntroNode)) return;
|
||||
if (!this.isHomeRuntimeAlive() || cc.fx.GameConfig.isGameplayEntryPreparing()) return;
|
||||
private openPassCheckIntro(onOpened?: () => void, onFinished?: () => void) {
|
||||
if (this.passContentLoading || (this.passCheckNode && cc.isValid(this.passCheckNode))) { if (onFinished) onFinished(); return; }
|
||||
if (this.passIntroNode && cc.isValid(this.passIntroNode)) { if (onFinished) onFinished(); return; }
|
||||
if (!this.isHomeRuntimeAlive() || cc.fx.GameConfig.isGameplayEntryPreparing()) { if (onFinished) onFinished(); return; }
|
||||
// 自动介绍不共用手动弹窗的请求编号,避免冷加载时被其他首页弹窗取消。
|
||||
if (!JiaZai.cachedPassIntroPrefab || !cc.isValid(JiaZai.cachedPassIntroPrefab, true) || !this.isHomeBundleReady('passCheck')) {
|
||||
this.loadHomePopupPrefab('passCheck', 'prefab/passCheckWindow', 'cachedPassIntroPrefab', 1).then(
|
||||
() => this.openPassCheckIntro(onOpened),
|
||||
error => cc.warn('[战令每日弹窗] 介绍窗口加载失败', error));
|
||||
() => this.openPassCheckIntro(onOpened, onFinished),
|
||||
error => { cc.warn('[战令每日弹窗] 介绍窗口加载失败', error); if (onFinished) onFinished(); });
|
||||
return;
|
||||
}
|
||||
const intro = this.passIntroNode = cc.instantiate(JiaZai.cachedPassIntroPrefab);
|
||||
|
|
@ -4637,6 +4651,7 @@ export default class JiaZai extends cc.Component {
|
|||
intro.scale = cc.fx.GameTool.adaptation();
|
||||
intro.zIndex = 1000;
|
||||
if (onOpened) onOpened();
|
||||
if (onFinished) onFinished();
|
||||
intro.getChildByName('close').on('click', () => { intro.destroy(); this.passIntroNode = null; }, this);
|
||||
intro.getChildByName('beginBtn').once('click', () => {
|
||||
intro.destroy(); this.passIntroNode = null;
|
||||
|
|
|
|||
|
|
@ -185,12 +185,18 @@ export default class SevenDayGiftHost extends cc.Component {
|
|||
});
|
||||
};
|
||||
|
||||
const loadedBundle = cc.assetManager.getBundle(BUNDLE_NAME);
|
||||
if (loadedBundle) {
|
||||
loadPrefab(loadedBundle);
|
||||
// 先加载活动分包的完整依赖,再解析 Prefab;已缓存的分包也需检查依赖。
|
||||
const canvas = cc.find("Canvas");
|
||||
const home: any = canvas && canvas.getComponent("JiaZai");
|
||||
if (!home || typeof home.loadHomeBundleWithDependencies !== "function") {
|
||||
this.finishGiftLoad(new Error("主页分包加载器不可用"));
|
||||
return;
|
||||
}
|
||||
cc.assetManager.loadBundle(BUNDLE_NAME, (error: Error, bundle: cc.AssetManager.Bundle) => {
|
||||
home.loadHomeBundleWithDependencies(BUNDLE_NAME, 5, (error: Error, bundle: cc.AssetManager.Bundle) => {
|
||||
if (loadId !== this.giftLoadId || !cc.isValid(this.node, true)) {
|
||||
if (bundle) this.releaseBundle(bundle);
|
||||
return;
|
||||
}
|
||||
if (error || !bundle) {
|
||||
this.finishGiftLoad(error || new Error("seven_day_gift bundle is missing"));
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -37,7 +37,8 @@ export default class HomePopupQueue extends cc.Component {
|
|||
if (seven && seven.children.some(visible)) return true;
|
||||
const home: any = this.node.getComponent("JiaZai");
|
||||
if (!home) return false;
|
||||
return ["RewardNode", "getcard", "monthlyCardNode", "actionpNode", "winStreakNode", "passCheckNode", "jungleTreasureNode"]
|
||||
if (home.passContentLoading) return true;
|
||||
return ["RewardNode", "getcard", "monthlyCardNode", "actionpNode", "winStreakNode", "passCheckNode", "passIntroNode", "jungleTreasureNode"]
|
||||
.some(key => visible(home[key]));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +0,0 @@
|
|||
{
|
||||
"ver": "1.1.3",
|
||||
"uuid": "49407d68-e1eb-4535-b644-f0f4e720c07a",
|
||||
"importer": "folder",
|
||||
"isBundle": false,
|
||||
"bundleName": "",
|
||||
"priority": 1,
|
||||
"compressionType": {},
|
||||
"optimizeHotUpdate": {},
|
||||
"inlineSpriteFrames": {},
|
||||
"isRemoteBundle": {},
|
||||
"subMetas": {}
|
||||
}
|
||||
|
|
@ -16,6 +16,7 @@ class Node {
|
|||
constructor(name = '') { Object.assign(this, { name, children: [], components: [], events: {}, active: true, x: 0, y: 0, width: 1080, height: 350 }); }
|
||||
set parent(value) { if (this._parent) this._parent.children = this._parent.children.filter(n => n !== this); this._parent = value; if (value) value.children.push(this); }
|
||||
get parent() { return this._parent; }
|
||||
get activeInHierarchy() { return this.active && !this.destroyed && (!this.parent || this.parent.activeInHierarchy); }
|
||||
get childrenCount() { return this.children.length; }
|
||||
addChild(node) { node.parent = this; }
|
||||
getChildByName(name) { return this.children.find(n => n.name === name) || null; }
|
||||
|
|
@ -129,12 +130,12 @@ test('manual rapid clicks queue each permitted lane once and reject level 36 for
|
|||
assert.equal(manager.stage.free[34], 0); assert.equal(manager.stage.passCheck[34], 0); assert.equal(manager.stage.free[35], 1);
|
||||
});
|
||||
|
||||
function homeFixture() {
|
||||
function homeFixture(queueOverride) {
|
||||
const { cc, child } = runtime();
|
||||
const parsed = ts.createSourceFile('JiaZai.ts', fs.readFileSync('assets/Script/JiaZai.ts', 'utf8'), 99, true);
|
||||
const members = parsed.statements.find(ts.isClassDeclaration).members.filter(n => /^(popUpPassCheck|openPassCheck|openPassCheckIntro|openPassCheckContent)$/.test(n.name?.getText(parsed))).map(n => n.getText(parsed)).join('\n');
|
||||
const callbacks = [];
|
||||
const context = { cc, passTier: model.passTier, console: quiet, Utils: { getPassCheckInfo: fn => callbacks.push(fn) }, MiniGameSdk: { API: { showToast() {} } }, setTimeout: () => 1, clearTimeout() {} };
|
||||
const context = { HomePopupQueue: queueOverride || { get: () => ({ enqueue: (key, priority, run) => run(() => {}) }) }, cc, passTier: model.passTier, console: quiet, Utils: { getPassCheckInfo: fn => callbacks.push(fn) }, MiniGameSdk: { API: { showToast() {} } }, setTimeout: () => 1, clearTimeout() {} };
|
||||
vm.runInNewContext(ts.transpileModule('class JiaZai {' + members + '} globalThis.Home=JiaZai;', { compilerOptions: { target: ts.ScriptTarget.ES2020 } }).outputText, context);
|
||||
const Home = context.Home; Home.cachedPassIntroPrefab = () => { const node = new Node('intro'); child(node, 'close'); child(node, 'beginBtn'); return node; };
|
||||
Home.cachedPassCheckPrefab = () => { const node = new Node('content'); node.components.push({ constructor: { name: 'passCheck' }, setInfo() {} }); return node; };
|
||||
|
|
@ -238,3 +239,68 @@ test('daily intro retries unavailable data then opens for a level 43 free player
|
|||
assert.equal(home.node.children.filter(n => n.name === 'intro').length, 1);
|
||||
assert.equal(scheduled.length, 0);
|
||||
});
|
||||
|
||||
|
||||
test('battle pass waits for seven-day popup and blocks later popups until closed', () => {
|
||||
let queue;
|
||||
const { home, cc, callbacks } = homeFixture({ get: () => queue });
|
||||
const Queue = load('assets/home_popup_queue/HomePopupQueue.ts', {}, { cc }).default;
|
||||
queue = new Queue(); queue.node = home.node; home.node.components.push(home);
|
||||
const seven = new Node('sevenDayGift'); home.node.addChild(seven); seven.addChild(new Node('activity'));
|
||||
Object.assign(cc.fx.GameConfig.GM_INFO, { level: 43, canIos: true, uid: 'queue-test' });
|
||||
home.deferHomePopupWhileTransfer = () => false;
|
||||
const local = new Map(); cc.fx.StorageMessage = { getStorage: k => local.get(k), setStorage: (k,v) => local.set(k,v) };
|
||||
home.popUpPassCheck(); callbacks.shift()({ code: 1, data: { upgradeReady: true, time: Date.now()-1000, passCheck: 'null' } });
|
||||
queue.update(); assert.equal(home.passIntroNode, undefined); assert.equal(local.size, 0);
|
||||
seven.removeAllChildren(); queue.update(); assert.ok(home.passIntroNode); assert.equal(local.size, 1);
|
||||
let later = false; queue.enqueue('later', 70, done => { later = true; done(); });
|
||||
queue.update(); assert.equal(later, false);
|
||||
home.passIntroNode.getChildByName('close').emit('click'); queue.update(); assert.equal(later, true);
|
||||
});
|
||||
|
||||
test('failed battle-pass loading releases queue completion without recording display', async () => {
|
||||
const { home, cc } = homeFixture(); let finished = 0, shown = 0;
|
||||
cc.warn = () => {};
|
||||
home.isHomeBundleReady = () => false;
|
||||
home.loadHomePopupPrefab = () => Promise.reject(new Error('offline'));
|
||||
home.openPassCheckIntro(() => shown++, () => finished++);
|
||||
await Promise.resolve(); assert.equal(finished, 1); assert.equal(shown, 0);
|
||||
});
|
||||
|
||||
test('battle-pass content loading blocks the next automatic popup', () => {
|
||||
const { cc } = runtime(); const Queue = load('assets/home_popup_queue/HomePopupQueue.ts', {}, { cc }).default;
|
||||
const home = { passContentLoading: true }; const queue = new Queue();
|
||||
queue.node = { getChildByName: () => null, getComponent: () => home };
|
||||
let ran = false; queue.enqueue('later', 70, done => { ran = true; done(); });
|
||||
queue.update(); assert.equal(ran, false); home.passContentLoading = false; queue.update(); assert.equal(ran, true);
|
||||
});
|
||||
|
||||
test('daily battle pass preloads before its queue turn without opening or recording', async () => {
|
||||
let run, resolveLoad, loads = 0, ready = false;
|
||||
const { home, cc, callbacks } = homeFixture({ get: () => ({ enqueue: (key, priority, task) => { run = task; } }) });
|
||||
Object.assign(cc.fx.GameConfig.GM_INFO, { level: 43, canIos: true, uid: 'preload-player' });
|
||||
home.deferHomePopupWhileTransfer = () => false;
|
||||
const local = new Map(); cc.fx.StorageMessage = { getStorage: k => local.get(k), setStorage: (k,v) => local.set(k,v) };
|
||||
home.isHomeBundleReady = () => ready;
|
||||
home.loadHomePopupPrefab = () => { loads++; return new Promise(resolve => { resolveLoad = () => { ready = true; resolve(); }; }); };
|
||||
home.popUpPassCheck(); callbacks.shift()({ code: 1, data: { upgradeReady: true, time: Date.now()-1000, passCheck: 'null' } });
|
||||
assert.equal(loads, 1); assert.equal(home.passIntroNode, undefined); assert.equal(local.size, 0);
|
||||
resolveLoad(); await Promise.resolve();
|
||||
assert.equal(home.passIntroNode, undefined); assert.equal(local.size, 0);
|
||||
let finished = false; run(() => finished = true); await Promise.resolve();
|
||||
assert.ok(home.passIntroNode); assert.equal(local.size, 1); assert.equal(loads, 1); assert.equal(finished, true);
|
||||
});
|
||||
|
||||
test('daily battle pass preload rejection releases its queue turn without recording', async () => {
|
||||
let run;
|
||||
const { home, cc, callbacks } = homeFixture({ get: () => ({ enqueue: (key, priority, task) => { run = task; } }) });
|
||||
cc.warn = () => {};
|
||||
Object.assign(cc.fx.GameConfig.GM_INFO, { level: 43, canIos: true, uid: 'preload-fail' });
|
||||
home.deferHomePopupWhileTransfer = () => false;
|
||||
const local = new Map(); cc.fx.StorageMessage = { getStorage: k => local.get(k), setStorage: (k,v) => local.set(k,v) };
|
||||
home.isHomeBundleReady = () => false;
|
||||
home.loadHomePopupPrefab = () => Promise.reject(new Error('offline'));
|
||||
home.popUpPassCheck(); callbacks.shift()({ code: 1, data: { upgradeReady: true, time: Date.now()-1000, passCheck: 'null' } });
|
||||
await Promise.resolve(); let finished = false; run(() => finished = true); await Promise.resolve();
|
||||
assert.equal(finished, true); assert.equal(local.size, 0); assert.equal(home.passIntroNode, undefined);
|
||||
});
|
||||
|
|
|
|||
42
tools/test-seven-day-loading.cjs
Normal file
42
tools/test-seven-day-loading.cjs
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
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 fixture(cached = false) {
|
||||
const bundles = new Map(), pending = new Map(); let parses = 0;
|
||||
const bundle = (name, deps = []) => ({name, deps, releaseAll() {}, load(path, type, done) {
|
||||
parses++; assert.ok(bundles.has('action_bundle')); assert.ok(bundles.has('UI')); done(null, {});
|
||||
}});
|
||||
const gift = bundle('seven_day_gift', ['action_bundle']);
|
||||
if (cached) bundles.set(gift.name, gift);
|
||||
const view = {};
|
||||
const cc = { _decorator: { ccclass: x => x }, Component: class {}, Prefab: class {},
|
||||
isValid: n => n && !n.destroyed,
|
||||
assetManager: { getBundle: name => bundles.get(name), removeBundle: b => bundles.delete(b.name),
|
||||
loadBundle: (name, options, cb) => { pending.set(name, typeof options === 'function' ? options : cb); } },
|
||||
instantiate: () => ({active: false, setPosition() {}, getComponent: () => view}),
|
||||
find: () => ({getComponent: () => home}) };
|
||||
const parsed = ts.createSourceFile('JiaZai.ts', fs.readFileSync('assets/Script/JiaZai.ts','utf8'),99,true);
|
||||
const members = parsed.statements.find(ts.isClassDeclaration).members.filter(n => ['isHomeBundleReady','loadHomeBundleWithDependencies'].includes(n.name?.getText(parsed))).map(n=>n.getText(parsed)).join('\n');
|
||||
const ctx = {cc}; vm.runInNewContext(ts.transpileModule('class Home {'+members+'} globalThis.Home=Home;', {compilerOptions:{target:ts.ScriptTarget.ES2020}}).outputText,ctx);
|
||||
const home = new ctx.Home(); const out = {};
|
||||
vm.runInNewContext(ts.transpileModule(fs.readFileSync('assets/Script/seven_day_gift/SevenDayGiftHost.ts','utf8'),{compilerOptions:{target:ts.ScriptTarget.ES2020,module:ts.ModuleKind.CommonJS,experimentalDecorators:true}}).outputText,{cc,exports:out,require:()=>({default:{}})});
|
||||
const host = new out.default(); host.node = {addChild() {}};
|
||||
const finish = (name,error) => { const b=name==='seven_day_gift'?gift:bundle(name,name==='action_bundle'?['UI']:[]);if(!error)bundles.set(name,b);const cb=pending.get(name);assert.ok(cb,'missing request '+name);pending.delete(name);cb(error,b); };
|
||||
return {host, pending, finish, parses:()=>parses};
|
||||
}
|
||||
for (const cached of [false,true]) test(`seven-day Prefab waits for nested dependencies, cached=${cached}`,()=>{
|
||||
const f=fixture(cached);let completed=0;f.host.ensureGiftLoaded(error=>{assert.ifError(error);completed++;});
|
||||
if(!cached)f.finish('seven_day_gift');assert.equal(f.parses(),0);
|
||||
f.finish('action_bundle');assert.equal(f.parses(),0);f.finish('UI');assert.equal(f.parses(),1);assert.equal(completed,1);
|
||||
});
|
||||
test('dependency failure completes callback and allows retry',()=>{
|
||||
const f=fixture(true);let error;f.host.ensureGiftLoaded(e=>error=e);f.finish('action_bundle',new Error('offline'));
|
||||
assert.match(error.message,/offline/);assert.equal(f.host.giftLoading,false);assert.equal(f.parses(),0);
|
||||
f.host.ensureGiftLoaded(e=>assert.ifError(e));f.finish('action_bundle');f.finish('UI');assert.equal(f.parses(),1);
|
||||
});
|
||||
test('destroyed host does not parse the Prefab after dependencies finish',()=>{
|
||||
const f=fixture(true);f.host.ensureGiftLoaded(()=>assert.fail('stale callback'));f.host.node.destroyed=true;
|
||||
f.finish('action_bundle');f.finish('UI');assert.equal(f.parses(),0);
|
||||
});
|
||||
Loading…
Reference in New Issue
Block a user