fix: 首页弹窗报错
This commit is contained in:
@@ -54,8 +54,8 @@ export default class HomePopupQueue extends cc.Component {
|
||||
if (this.policy.record.monthlyRewardPending) this.policy.request('monthlyReward');
|
||||
HomePopupQueue.loginPending = false;
|
||||
}
|
||||
completePreparation(source: string): void { this.sources.delete(source); }
|
||||
holdPreparation(source: string): void { if (this.collecting) this.sources.add(source); }
|
||||
completePreparation(source: string): void { if (this.sources) this.sources.delete(source); }
|
||||
holdPreparation(source: string): void { if (this.collecting && this.sources) this.sources.add(source); }
|
||||
start(): void {
|
||||
if (!this.node.getChildByName('sevenDayGift')) this.completePreparation('seven');
|
||||
}
|
||||
@@ -66,26 +66,28 @@ export default class HomePopupQueue extends cc.Component {
|
||||
&& this.policy.eligible(key, typeof type === 'function' ? type() : type);
|
||||
}
|
||||
unavailable(key: string): void {
|
||||
if (this.policy.ended || this.recordKey !== HomePopupQueue.storageKey()
|
||||
if (!this.policy || this.policy.ended || this.recordKey !== HomePopupQueue.storageKey()
|
||||
|| this.accountToken !== cc.fx.GameConfig.GM_INFO.token || this.session !== HomePopupQueue.sessions[this.recordKey]) return;
|
||||
this.policy.unavailable(key);
|
||||
}
|
||||
requestPresentation(key: string): boolean {
|
||||
if (this.policy.ended || this.recordKey !== HomePopupQueue.storageKey()
|
||||
if (!this.policy || this.policy.ended || this.recordKey !== HomePopupQueue.storageKey()
|
||||
|| this.accountToken !== cc.fx.GameConfig.GM_INFO.token || this.session !== HomePopupQueue.sessions[this.recordKey]) return false;
|
||||
return this.policy.request(key);
|
||||
}
|
||||
wasShownToday(key: string): boolean { return !!this.policy.record.shown[key]; }
|
||||
wasShownToday(key: string): boolean { return !!this.policy && !!this.policy.record.shown[key]; }
|
||||
markShown(key: string): void { if (this.isRunning(key)) this.policy.shown(key); }
|
||||
isRunning(key: string): boolean {
|
||||
return !this.policy.ended && this.running === key && !cc.fx.GameConfig.isGameplayEntryPreparing()
|
||||
// Late network/load callbacks may outlive Cocos clearing the component's object fields.
|
||||
return !!this.policy && !this.policy.ended && this.running === key && !cc.fx.GameConfig.isGameplayEntryPreparing()
|
||||
&& this.recordKey === HomePopupQueue.storageKey() && this.accountToken === cc.fx.GameConfig.GM_INFO.token
|
||||
&& this.session === HomePopupQueue.sessions[this.recordKey];
|
||||
}
|
||||
/** A manual popup may have opened while this automatic task was loading. */
|
||||
canPresent(key: string): boolean { return this.isRunning(key) && !this.hasVisiblePopup(key); }
|
||||
get monthlyRewardPending(): boolean { return !!this.policy.record.monthlyRewardPending; }
|
||||
get monthlyRewardPending(): boolean { return !!this.policy && !!this.policy.record.monthlyRewardPending; }
|
||||
setMonthlyRewardPending(): void {
|
||||
if (!this.policy) return;
|
||||
this.policy.record.monthlyRewardPending = true;
|
||||
const current: PopupRecord = cc.fx.StorageMessage.getStorage(this.recordKey) || this.policy.record;
|
||||
current.monthlyRewardPending = true;
|
||||
@@ -93,14 +95,17 @@ export default class HomePopupQueue extends cc.Component {
|
||||
this.requestPresentation('monthlyReward');
|
||||
}
|
||||
cancelBatch(): void {
|
||||
this.policy.end(); this.collecting = false; this.tasks.length = 0; this.running = '';
|
||||
if (this.policy) this.policy.end();
|
||||
this.collecting = false;
|
||||
if (this.tasks) this.tasks.length = 0;
|
||||
this.running = '';
|
||||
const cancel = this.cancelRun; this.cancelRun = null;
|
||||
try { if (cancel) cancel(); }
|
||||
finally { if (this.backdrop && cc.isValid(this.backdrop, true)) this.backdrop.release(this); }
|
||||
}
|
||||
|
||||
public has(key: string): boolean {
|
||||
return this.running === key || this.tasks.some(task => task.key === key);
|
||||
return this.running === key || !!this.tasks && this.tasks.some(task => task.key === key);
|
||||
}
|
||||
|
||||
public static get(canvas: cc.Node): HomePopupQueue {
|
||||
@@ -108,9 +113,10 @@ export default class HomePopupQueue extends cc.Component {
|
||||
}
|
||||
|
||||
public enqueue(key: string, priority: number, run: PopupRun, type?: PopupType): boolean {
|
||||
if (!this.policy || this.policy.ended) return false;
|
||||
const managed = !!HOME_POPUP_RULES[key];
|
||||
if (managed && !this.canCollect(key, type)) return false;
|
||||
if (this.policy.ended || this.running === key || this.tasks.some(task => task.key === key)) return false;
|
||||
if (this.running === key || this.tasks.some(task => task.key === key)) return false;
|
||||
// Keep accepted daily/login candidates pending until actual visibility, including load failures.
|
||||
this.policy.request(key, typeof type === 'function' ? type() : type);
|
||||
priority = key === 'ordersAfterPassSettlement' ? 33.25
|
||||
|
||||
@@ -236,6 +236,49 @@ test('relogin invalidates old queue callbacks even when account and token are un
|
||||
assert.equal(current.policy.hasPending('starterPack'), true, 'old callbacks cannot discard a current pending request');
|
||||
});
|
||||
|
||||
test('late task callbacks are harmless after destroyed component fields are cleared', () => {
|
||||
const h = harness(), old = h.create();
|
||||
let finish, continueBackdrop, cancelled = 0;
|
||||
old.enqueue('starterPack', 0, (done, keepBackdrop) => {
|
||||
finish = done; continueBackdrop = keepBackdrop;
|
||||
return () => { cancelled++; done(); };
|
||||
});
|
||||
h.ready(old); old.update(); old.onDestroy();
|
||||
assert.equal(cancelled, 1);
|
||||
// Cocos clears object references after onDestroy; requests still retain their callbacks.
|
||||
for (const key of ['policy', 'sources', 'tasks', 'session', 'backdrop', 'node']) old[key] = null;
|
||||
const current = h.create(false), saved = structuredClone([...h.storage]);
|
||||
finish(); continueBackdrop(); old.markShown('starterPack'); old.unavailable('starterPack');
|
||||
old.completePreparation('starter'); old.holdPreparation('starter');
|
||||
old.setMonthlyRewardPending(); old.cancelBatch(); old.update();
|
||||
assert.equal(old.requestPresentation('starterPack'), false);
|
||||
assert.equal(old.canCollect('starterPack'), false);
|
||||
assert.equal(old.isRunning('starterPack'), false);
|
||||
assert.equal(old.canPresent('starterPack'), false);
|
||||
assert.equal(old.wasShownToday('starterPack'), false);
|
||||
assert.equal(old.monthlyRewardPending, false);
|
||||
assert.equal(old.has('starterPack'), false);
|
||||
for (const key of ['starterPack', 'ordersAfterPassSettlement'])
|
||||
assert.equal(old.enqueue(key, 0, () => assert.fail('destroyed task ran')), false);
|
||||
assert.deepEqual([...h.storage], saved, 'late callbacks cannot change persisted records');
|
||||
assert.equal(current.policy.hasPending('starterPack'), true);
|
||||
assert.equal(current.enqueue('starterPack', 0, done => { current.markShown('starterPack'); done(); }), true);
|
||||
h.ready(current); current.update();
|
||||
assert.equal(current.wasShownToday('starterPack'), true, 'the next home can retry the unfinished popup');
|
||||
});
|
||||
|
||||
test('queue callbacks before onLoad reject work without an initialized policy', () => {
|
||||
const h = harness(), q = new h.Queue();
|
||||
q.unavailable('starterPack'); q.markShown('starterPack'); q.setMonthlyRewardPending();
|
||||
assert.equal(q.requestPresentation('starterPack'), false);
|
||||
assert.equal(q.isRunning('starterPack'), false);
|
||||
assert.equal(q.wasShownToday('starterPack'), false);
|
||||
assert.equal(q.monthlyRewardPending, false);
|
||||
assert.equal(q.enqueue('ordersAfterPassSettlement', 0, () => assert.fail('uninitialized task ran')), false);
|
||||
q.onDestroy();
|
||||
assert.equal(h.storage.size, 0);
|
||||
});
|
||||
|
||||
for (const scenario of ['four_failures', 'level_purchase', 'leave_during_activation']) test(`lucky offer: deferred ${scenario} activation returns through the real home adapter`, () => {
|
||||
const reason = scenario === 'leave_during_activation' ? 'four_failures' : scenario;
|
||||
const h = harness(), first = h.create(); h.ready(first); first.update(); first.onDestroy();
|
||||
|
||||
Reference in New Issue
Block a user