import { CoroutineV2 } from "../Engine/CatanEngine/CoroutineV2/CoroutineV2"; import LocalStorageData from "../Engine/Data/LocalStorageData"; import UpdatePanel from "../UpdatePanel"; import BusinessTypeSetting from "../_BusinessTypeSetting/BusinessTypeSetting"; import Enum_HUDM from "./Enum_HUDM"; const { ccclass, property } = cc._decorator; /** HUDManager */ @ccclass export default class HUDM extends cc.Component { //#region static 屬性 private static _instance: HUDM = null; public static get Instance(): HUDM { return HUDM._instance; } //#endregion //#region private 屬性 private _updatePanel: UpdatePanel; private _am: jsb.AssetsManager; private _onFileProgress: (finish: number, total: number, item: string) => void; private _updateListener: any; private _checkListener: any; private _versionCompareHandle: any = null; private _needUpdateData: Enum_HUDM.NeedUpdateDataObj = null; private _updateingData: Enum_HUDM.UpdateingDataObj = null; private _updating: boolean = false; private _canRetry: boolean = false; private _isChangeUrl: boolean = false; private _isNewBundle: boolean = false; private _path: string = "Bundle"; private _customManifest: string = ""; private _storagePath: string = ""; //#endregion //#region Lifecycle constructor(...params: any[]) { super(); if (!cc.sys.isNative) { return; } HUDM._instance = this; this._updatePanel = params[0]; this._isNewBundle = params[1]; // let packageUrl: string = `https://jianmiau.tk/Resources/App/JMKA/update/remote-assets/${BusinessTypeSetting.COMPILE_VERSION}`; let packageUrl: string = BusinessTypeSetting.UsePatch; this.CheckCompileVersion(); this.CheckChangePatchUrl(); this._customManifest = JSON.stringify({ "packageUrl": packageUrl, "remoteManifestUrl": `${packageUrl}/project.manifest`, "remoteVersionUrl": `${packageUrl}/version.json`, "version": "0.0.0", }); this._storagePath = `${(jsb.fileUtils ? jsb.fileUtils.getWritablePath() : "./")}${this._path}`; // 本地熱更目錄下已存在project.manifest,則直接修改已存在的project.manifest if (this._isChangeUrl) { if (jsb.fileUtils.isFileExist(this._storagePath + "/project.manifest")) { this._isChangeUrl = true; this._modifyAppLoadUrlForManifestFile(this._storagePath, packageUrl); } } this._versionCompareHandle = function (versionA: string, versionB: string): number { // console.log("Ver A " + versionA + "VerB " + versionB); let vA: string[] = versionA.split("."); let vB: string[] = versionB.split("."); // 長度不相等,則進行更新 if (vA.length !== vB.length) { return -1; } for (let i: number = 0; i < vA.length; ++i) { let a: number = +vA[i]; let b: number = +vB[i] || 0; if (a === b) { // 數字相同,則跳過 continue; } else { // 數字不同,則進行更新 return -1; } } // 長度相等且數字相等,則不更新 return 0; }; this._initAssetManaget(); } private _initAssetManaget(): void { let self: this = this; // this._am = new jsb.AssetsManager("", this._storagePath, this._versionCompareHandle); // Setup the verification callback, but we don't have md5 check function yet, so only print some message // Return true if the verification passed, otherwise return false this._am.setVerifyCallback(function (path: any, asset: { compressed: any; md5: any; path: any; size: any; }): boolean { // When asset is compressed, we don't need to check its md5, because zip file have been deleted. let compressed: any = asset.compressed; // Retrieve the correct md5 value. let expectedMD5: string = asset.md5; // asset.path is relative path and path is absolute. let relativePath: string = asset.path; // The size of asset file, but this value could be absent. let size: any = asset.size; if (compressed) { self._updatePanel.info.string = "Verification passed : " + relativePath; // console.log("onLoad -> Verification passed : " + relativePath); return true; } else { self._updatePanel.info.string = "Verification passed : " + relativePath + " (" + expectedMD5 + ")"; // console.log("onLoad -> setVerifyCallbackVerification passed : " + relativePath + " (" + expectedMD5 + ")"); return true; } }); if (cc.sys.os === cc.sys.OS_ANDROID) { // Some Android device may slow down the download process when concurrent tasks is too much. // The value may not be accurate, please do more test and find what's most suitable for your game. // this._am.setMaxConcurrentTask(10); this._am["setMaxConcurrentTask"](10); // this._updatePanel.info.string = "Max concurrent tasks count have been limited to 2"; // console.log("onLoad -> Max concurrent tasks count have been limited to 10"); } } private _modifyAppLoadUrlForManifestFile(filePath: string, newBundleUrl: string): void { let allpath: string[] = [filePath, filePath + "_temp"]; let manifestname: string[] = ["project.manifest", "project.manifest.temp"]; for (var i: number = 0; i < allpath.length; ++i) { let path: string = `${allpath[i]}/${manifestname[i]}`; if (jsb.fileUtils.isFileExist(path)) { // console.log(`[HUD] modifyAppLoadUrlForManifestFile: 有下載的manifest文件,直接修改熱更地址`); // 修改project.manifest let projectManifest: string = jsb.fileUtils.getStringFromFile(path); let projectManifestObj: any = JSON.parse(projectManifest); projectManifestObj.packageUrl = newBundleUrl; projectManifestObj.remoteManifestUrl = newBundleUrl + "/project.manifest"; projectManifestObj.remoteVersionUrl = newBundleUrl + "/version.json"; let afterString: string = JSON.stringify(projectManifestObj); jsb.fileUtils.writeStringToFile(afterString, path); } } } //#endregion public *CheckUpdate(): IterableIterator { this._needUpdateData = null; if (this._updating) { this._updatePanel.info.string = "Checking or updating ..."; console.error("checkUpdate -> Checking or updating ..."); return; } if (this._am.getState() === jsb.AssetsManager.State.UNINITED) { let manifest: jsb.Manifest = new jsb.Manifest(this._customManifest, this._storagePath); this._am.loadLocalManifest(manifest, this._storagePath); } if (!this._am.getLocalManifest() || !this._am.getLocalManifest().isLoaded()) { // this.tipsLabel.string = "Failed to load local manifest ..."; console.error("checkUpdate -> Failed to load local manifest ..."); return; } this._am.setEventCallback(this.checkCb.bind(this)); this._am.checkUpdate(); this._updating = true; while (this._needUpdateData === null) { yield null; } let newBundleUrl: string = BusinessTypeSetting.UsePatch; this._modifyAppLoadUrlForManifestFile(this._storagePath, newBundleUrl); this._initAssetManaget(); let manifest: jsb.Manifest = new jsb.Manifest(this._customManifest, this._storagePath); this._am.loadLocalManifest(manifest, this._storagePath); if (!this._am.getLocalManifest() || !this._am.getLocalManifest().isLoaded()) { // this.tipsLabel.string = "Failed to load local manifest ..."; console.error("checkUpdate -> Failed to load local manifest ..."); return; } // 更新動態路徑後再跑一次 this._am.setEventCallback(this.checkCb.bind(this)); this._needUpdateData = null; this._am.checkUpdate(); this._updating = true; while (this._needUpdateData === null) { yield null; } if ((this._isChangeUrl || this._isNewBundle) && ((!this._needUpdateData.IsNeedUpdate && this._needUpdateData.TotalBytes !== "failed") || this._needUpdateData.TotalBytes === "0 B")) { if (jsb.fileUtils.isFileExist(this._storagePath)) { let isremoveDirectory: boolean = jsb.fileUtils.removeDirectory(this._storagePath); let isremoveDirectory_temp: boolean = jsb.fileUtils.removeDirectory(this._storagePath + "_temp"); if (isremoveDirectory_temp) { console.log(`removeDirectory: ${this._storagePath}_temp`); } if (isremoveDirectory) { console.log(`removeDirectory: ${this._storagePath}`); this._needUpdateData = null; this._initAssetManaget(); this._needUpdateData = yield* this.CheckUpdate(); } } } return this._needUpdateData; } private checkCb(event: jsb.EventAssetsManager): void { let failed: boolean = false; switch (event.getEventCode()) { case jsb.EventAssetsManager.ERROR_NO_LOCAL_MANIFEST: console.error("checkCb -> No local manifest file found, HUD skipped."); failed = true; break; case jsb.EventAssetsManager.ERROR_DOWNLOAD_MANIFEST: case jsb.EventAssetsManager.ERROR_PARSE_MANIFEST: console.error("checkCb -> Fail to download manifest file, HUD skipped."); failed = true; break; case jsb.EventAssetsManager.ALREADY_UP_TO_DATE: console.log("checkCb -> Already up to date with the latest remote version."); this._needUpdateData = new Enum_HUDM.NeedUpdateDataObj(false); break; case jsb.EventAssetsManager.NEW_VERSION_FOUND: this._updatePanel.checkBtn.active = false; this._updatePanel.fileProgress.progress = 0; this._updatePanel.byteProgress.progress = 0; this._updatePanel.info.string = "發現新版本,請嘗試更新。 " + this._bytesToSize(event.getTotalBytes()); console.log("checkCb -> New version found, please try to update." + event.getTotalBytes()); this._needUpdateData = new Enum_HUDM.NeedUpdateDataObj(true, this._bytesToSize(event.getTotalBytes())); break; default: return; } this._am.setEventCallback(null); this._checkListener = null; this._updating = false; if (failed) { this._needUpdateData = new Enum_HUDM.NeedUpdateDataObj(false, "failed"); } } public *HUD(onFileProgress?: (finish: number, total: number, item: string) => void): IterableIterator { this._updatePanel.updateBtn.active = false; this._updateingData = null; if (this._am && !this._updating) { this._am.setEventCallback(this._updateCb.bind(this)); if (this._am.getState() === jsb.AssetsManager.State.UNINITED) { let manifest: jsb.Manifest = new jsb.Manifest(this._customManifest, this._storagePath); this._am.loadLocalManifest(manifest, this._storagePath); } this._onFileProgress = onFileProgress ? onFileProgress : null; this._am.update(); this._updating = true; while (this._updateingData === null) { yield null; } return this._updateingData; } else { return new Enum_HUDM.UpdateingDataObj(false); } } private _updateCb(event: jsb.EventAssetsManager): void { let self: this = this; let needRestart: boolean = false; let failed: boolean = false; switch (event.getEventCode()) { case jsb.EventAssetsManager.ERROR_NO_LOCAL_MANIFEST: this._updatePanel.info.string = "No local manifest file found, HUD skipped."; console.log("updateCb -> No local manifest file found, HUD skipped."); failed = true; break; case jsb.EventAssetsManager.UPDATE_PROGRESSION: this._updatePanel.byteProgress.progress = event.getPercent(); this._updatePanel.fileProgress.progress = event.getPercentByFile(); this._updatePanel.fileLabel.string = event.getDownloadedFiles() + " / " + event.getTotalFiles(); // this.tipsLabel.string = event.getDownloadedBytes() + " / " + event.getTotalBytes(); // console.log("updateCb -> " + event.getDownloadedBytes() + " / " + event.getTotalBytes()); // let msg: string = event.getMessage(); // if (msg) { // this._updatePanel.info.string = 'Updated file: ' + msg; // console.log("updateCb -> Updated file: " + msg); // console.log("updateCb -> " + event.getPercent() / 100 + "% : " + msg); // } let msg: string = event.getMessage(); if (this._onFileProgress) { this._onFileProgress(event.getDownloadedBytes(), event.getTotalBytes(), msg ? msg : ""); } break; case jsb.EventAssetsManager.ERROR_DOWNLOAD_MANIFEST: case jsb.EventAssetsManager.ERROR_PARSE_MANIFEST: this._updatePanel.info.string = "Fail to download manifest file, HUD skipped."; console.error("updateCb -> Fail to download manifest file, HUD skipped."); failed = true; break; case jsb.EventAssetsManager.ALREADY_UP_TO_DATE: this._updatePanel.info.string = "Already up to date with the latest remote version."; console.error("updateCb -> Already up to date with the latest remote version."); failed = true; break; case jsb.EventAssetsManager.UPDATE_FINISHED: // this.tipsLabel.string = "更新完成. " + event.getMessage(); console.log("updateCb -> 更新完成. " + event.getMessage()); this._updateingData = new Enum_HUDM.UpdateingDataObj(true); needRestart = true; break; case jsb.EventAssetsManager.UPDATE_FAILED: this._updatePanel.info.string = "Update failed. " + event.getMessage(); console.error("updateCb -> Update failed. " + event.getMessage()); this._updatePanel.retryBtn.active = true; this._canRetry = true; this._updateingData = new Enum_HUDM.UpdateingDataObj(false); this._updating = false; break; case jsb.EventAssetsManager.ERROR_UPDATING: this._updatePanel.info.string = "Asset update error: " + event.getAssetId() + ", " + event.getMessage(); console.error("updateCb -> Asset update error: " + event.getAssetId() + ", " + event.getMessage()); break; case jsb.EventAssetsManager.ERROR_DECOMPRESS: this._updatePanel.info.string = event.getMessage(); console.error("updateCb -> " + event.getMessage()); break; default: break; } if (failed) { this._am.setEventCallback(null); this._updateListener = null; this._updating = false; } if (needRestart) { let AsyncFunction: () => IterableIterator = function* (): IterableIterator { self._updatePanel.info.string = "更新完成 即將重啟"; // 卡個一幀不然都看不到100%的畫面 yield CoroutineV2.WaitTime(5 / cc.game.getFrameRate()).Start(); self._am.setEventCallback(null); self._updateListener = null; // Prepend the manifest's search path let searchPaths: string[] = jsb.fileUtils.getSearchPaths(); let newPaths: [string] = self._am.getLocalManifest().getSearchPaths(); console.log(JSON.stringify(newPaths)); Array.prototype.unshift.apply(searchPaths, newPaths); // This value will be retrieved and appended to the default search path during game startup, // please refer to samples/js-tests/main.js for detailed usage. // !!! Re-add the search paths in main.js is very important, otherwise, new scripts won't take effect. cc.sys.localStorage.setItem("HotUpdateSearchPaths", JSON.stringify(searchPaths)); jsb.fileUtils.setSearchPaths(searchPaths); cc.audioEngine.stopAll(); cc.game.restart(); }; CoroutineV2.Single(AsyncFunction()).Start(); } } public *RetryDownLoadFailedAssets(): IterableIterator { if (!this._updating && this._canRetry) { this._updateingData = null; this._updatePanel.retryBtn.active = false; this._canRetry = false; this._updatePanel.info.string = "Retry failed Assets..."; console.log("retry -> Retry failed Assets..."); this._am.downloadFailedAssets(); while (this._updateingData === null) { yield null; } return this._updateingData; } else { console.error(`retry -> error updating: ${this._updating}, canRetry: ${this._canRetry}`); this._updateingData = new Enum_HUDM.UpdateingDataObj(false); } } private _bytesToSize(bytes: number): string { if (bytes === 0) { return "0 B"; } let k: number = 1024; let sizes: string[] = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]; let i: number = Math.floor(Math.log(bytes) / Math.log(k)); return (bytes / Math.pow(k, i)).toPrecision(3) + " " + sizes[i]; } protected onDestroy(): void { if (this._updateListener) { this._am.setEventCallback(null); this._updateListener = null; } } //#region 清除資料 /** 判斷更改編譯版號.清除BUNDLE記錄 */ public CheckCompileVersion(): void { let oldCompileVersion: string = LocalStorageData.Instance.CompileVersion; let newCompileVersion: string = BusinessTypeSetting.COMPILE_VERSION; if (oldCompileVersion && oldCompileVersion !== newCompileVersion) { // this.ClearBundleData(); console.warn(`change compile version. ${oldCompileVersion} -> ${newCompileVersion}`); } LocalStorageData.Instance.CompileVersion = BusinessTypeSetting.COMPILE_VERSION; } /** 判斷更改PATCH環境.清除BUNDLE記錄 */ public CheckChangePatchUrl(): void { let oldBundleUrl: string = LocalStorageData.Instance.BundleUrl; let newBundleUrl: string = BusinessTypeSetting.UsePatch; if (oldBundleUrl && oldBundleUrl !== newBundleUrl) { // this.ClearBundleData(); console.warn(`change patch url. ${oldBundleUrl} -> ${newBundleUrl}`); this._isChangeUrl = true; } LocalStorageData.Instance.BundleUrl = BusinessTypeSetting.UsePatch; } //#endregion }