Files
MatchMaster/assets/Script/module/Task/DailyTaskSync.ts
T
2026-09-09 09:53:59 +08:00

243 lines
12 KiB
TypeScript

import Utils from "../Pay/Utils";
import DailyTaskProgressTracker from "./DailyTaskProgressTracker";
/** 任务内存缓存及串行同步。UI 读取缓存,进度保存与领奖共用同一条请求队列。 */
export default class DailyTaskSync {
private static loaded = false;
private static uid = "";
private static offset = 0;
private static revision = 0;
private static savedRevision = 0;
private static resets: { [taskId: string]: number } = {};
private static queue: Array<(done: () => void) => void> = [];
private static busy = false;
private static readers: Array<(response: any) => void> = null;
private static paused = false;
private static needsRefresh = false;
private static hiddenAt = 0;
static now(): number { return Date.now() + this.offset; }
static isExpired(): boolean {
const data = DailyTaskProgressTracker.getTaskData();
return !!data && [data.dayEndsAt, data.weekEndsAt]
.some(end => Number(end) > 0 && this.now() >= Number(end));
}
static cached(): any {
const data = DailyTaskProgressTracker.getTaskData();
return this.loaded && this.uid === this.currentUid() && !this.needsRefresh && !this.isExpired()
&& data && Number(data.dayEndsAt) > this.now() && Number(data.weekEndsAt) > this.now() ? data : null;
}
static markDirty(): void { this.revision++; this.notify(); }
static markReset(taskId: string): void { this.resets[taskId] = this.revision + 1; }
static read(callback: (response: any) => void, force = false): void {
const cache = this.cached();
if (!force && cache) { callback(this.success(cache)); return; }
if (this.readers) { this.readers.push(callback); return; }
this.readers = [callback];
this.enqueue(done => {
this.saveDirty(saved => {
if (saved.code !== 1) { this.finishRead(saved, done); return; }
const snapshot = this.snapshot();
const revision = this.revision;
const requestUid = this.currentUid();
Utils.getDailyQuestInfo(response => {
if (requestUid !== this.currentUid()) response = this.failure("登录用户已变更,请重新获取任务");
if (this.validResponse(response)) {
this.needsRefresh = false;
this.apply(response.data.task, snapshot, revision);
response.data.task = DailyTaskProgressTracker.getTaskData();
if (!this.cached()) {
this.needsRefresh = true;
response = this.failure("任务已过期或缺少刷新时间,请重试");
}
} else {
this.needsRefresh = true;
response = response && response.code !== 1 ? response : this.failure("任务数据无效,请重试");
}
this.finishRead(response, done);
});
});
});
}
static save(callback: (response: any) => void = () => {}): void {
this.enqueue(done => this.saveDirty(response => { done(); callback(response); }));
}
static claim(id: string, callback: (response: any) => void, scope: "daily" | "weekly" = "daily"): void {
if (this.paused) { callback(this.failure("任务测试已暂停,请先恢复任务同步")); return; }
// 结算在线进度;其保存请求和本次领奖将按顺序执行。
DailyTaskProgressTracker.flushNow(false);
this.enqueue(done => {
this.saveDirty(saved => {
if (saved.code !== 1) { done(); callback(saved); return; }
if (!this.cached()) {
done();
this.read(() => callback(this.failure("任务数据已刷新,请重新领取")), true);
return;
}
const snapshot = this.snapshot();
const revision = this.revision;
const taskData = DailyTaskProgressTracker.getTaskData();
const item = scope === "weekly" ? taskData.weeklyRewards.find(item => item.rewardId === id)
: taskData.dailyTasks.find(item => item.taskId === id);
if (!item || item.state !== 1) { done(); callback(this.failure("任务状态已变化,请查看最新任务")); return; }
const requestUid = this.currentUid();
Utils.getDailyQuestReward(id, response => {
if (requestUid !== this.currentUid()) response = this.failure("登录用户已变更");
if (this.validResponse(response)) {
this.apply(response.data.task, snapshot, revision);
response.data.task = DailyTaskProgressTracker.getTaskData();
} else {
this.needsRefresh = true;
response = response && response.code !== 1 ? response : this.failure("领奖返回数据异常,请刷新任务后重试");
}
// 先让领奖界面消费结果,再执行后续排队的同步。
try { callback(response); } finally { done(); }
}, scope);
});
});
}
static background(): void { this.hiddenAt = Date.now(); }
static networkChanged(connected: boolean): void {
if (!connected) this.needsRefresh = true;
else this.foreground();
}
static foreground(): void {
if (!this.loaded || this.paused) return;
const longAbsence = this.hiddenAt > 0 && Date.now() - this.hiddenAt >= 5 * 60 * 1000;
this.hiddenAt = 0;
if (longAbsence || !this.cached()) this.read(() => {}, true);
}
/** 微信开发者工具控制台:pause -> Postman 修改 -> refresh -> resume。 */
static debug(action: "pause" | "refresh" | "resume" | "status", callback: (response: any) => void): void {
if (cc.fx.GameTool.getWechatGameVersion() !== "开发版") {
callback(this.failure("任务调试仅限开发版")); return;
}
if (action === "status") {
callback({ code: 1, data: { paused: this.paused, busy: this.busy, cached: !!this.cached(), expiresAt: this.expiration() } });
return;
}
if ((action === "refresh" || action === "resume") && this.paused
&& this.revision !== this.savedRevision && !this.isExpired()) {
callback(this.failure("暂停前的进度尚未保存成功,请重新执行 pause")); return;
}
if (action === "pause") {
// 先结算,再暂停新事件;等待已发送请求和现有进度保存完成后才允许改服务器。
DailyTaskProgressTracker.flushNow(false);
this.paused = true;
DailyTaskProgressTracker.setDebugPaused(true);
this.enqueue(done => this.saveDirty(response => { done(); callback(response); }, true));
} else if (action === "refresh") {
this.read(callback, true);
} else if (action === "resume") {
this.read(response => {
if (response.code === 1) {
this.paused = false;
DailyTaskProgressTracker.setDebugPaused(false);
}
callback(response);
}, true);
} else callback(this.failure("未知任务调试操作"));
}
private static saveDirty(callback: (response: any) => void, allowPaused = false): void {
const data = DailyTaskProgressTracker.getTaskData();
if (!data || this.revision === this.savedRevision || (this.paused && !allowPaused)
|| this.isExpired() || (this.loaded && this.uid !== this.currentUid())) {
callback(this.success(data)); return;
}
const snapshot = this.snapshot();
const revision = this.revision;
const requestUid = this.currentUid();
Utils.setDailyQuestInfo(response => {
if (requestUid !== this.currentUid()) response = this.failure("登录用户已变更");
if (!this.validResponse(response)) {
this.needsRefresh = true;
callback(response && response.code !== 1 ? response : this.failure("任务进度保存失败"));
return;
}
this.savedRevision = revision;
this.needsRefresh = false;
DailyTaskProgressTracker.confirmSaved(response.data.task);
this.apply(response.data.task, snapshot, revision);
// 保存期间新增的事件仍保留在缓存中,继续提交后才允许领奖/强制读取。
this.saveDirty(callback, allowPaused);
}, snapshot);
}
private static apply(data: any, snapshot: any, revision: number): void {
const current = DailyTaskProgressTracker.getTaskData();
const accountChanged = this.loaded && this.uid !== this.currentUid();
const sameDay = !accountChanged && snapshot && current && data.dayKey === snapshot.dayKey && data.dayKey === current.dayKey;
if (sameDay) {
data.dailyTasks.forEach(task => {
const before = snapshot.dailyTasks.find(item => item.taskId === task.taskId);
const local = current.dailyTasks.find(item => item.taskId === task.taskId);
if (!before || !local || task.state === 2) return;
if (local.state === 2 && before.state !== 2) { task.state = 2; task.value = local.value; return; }
const delta = Number(local.value) - Number(before.value);
const reset = (this.resets[task.taskId] || 0) > revision;
if (!delta && local.state === before.state && !reset) return;
// 连胜可被清零,不能简单地取进度最大值。
task.value = Math.max(0, Math.min(task.target, reset || delta < 0 ? local.value : Number(task.value) + delta));
task.state = task.value >= task.target ? 1 : 0;
});
} else if (accountChanged || current && data.dayKey !== current.dayKey) {
this.revision = this.savedRevision = 0;
this.resets = {};
DailyTaskProgressTracker.changePeriod(accountChanged);
}
this.uid = this.currentUid();
this.loaded = true;
if (Number(data.serverNow) > 0) this.offset = Number(data.serverNow) - Date.now();
DailyTaskProgressTracker.setTaskData(data);
this.notify();
}
private static finishRead(response: any, done: () => void): void {
const readers = this.readers;
this.readers = null;
try { readers.forEach(callback => callback(response)); } finally { done(); }
}
private static enqueue(operation: (done: () => void) => void): void {
this.queue.push(operation); this.pump();
}
private static pump(): void {
if (this.busy || !this.queue.length) return;
this.busy = true;
this.queue.shift()(() => { this.busy = false; this.pump(); });
}
private static snapshot(): any {
const data = DailyTaskProgressTracker.getTaskData();
return data ? JSON.parse(JSON.stringify(data)) : null;
}
private static currentUid(): string {
return cc.fx.StorageMessage.getStorage("uid") || cc.fx.GameConfig.GM_INFO.uid || "";
}
private static validResponse(response: any): boolean {
const data = response && response.code === 1 && response.data && response.data.task;
return DailyTaskProgressTracker.isValidTaskData(data) && Array.isArray(data.weeklyRewards);
}
private static success(task: any): any { return { code: 1, data: { task } }; }
private static failure(msg: string): any { return { code: 0, msg }; }
private static expiration(): number {
const data = DailyTaskProgressTracker.getTaskData();
return data ? Math.min(Number(data.dayEndsAt) || 0, Number(data.weekEndsAt) || 0) : 0;
}
private static notify(): void {
if (cc.director && cc.director.emit) cc.director.emit("daily-tasks-changed");
}
}