import BusinessTypeSetting from "../../_BusinessTypeSetting/BusinessTypeSetting"; import Enum_Loading from "../HUDV2/Enum_Loading"; import AssetBundleMamager from "./AssetBundleMamager"; 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 static 屬性 public BundleName: string = ""; //#endregion //#region private 屬性 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_Loading.NeedUpdateDataObj = null; private _updateingData: Enum_Loading.UpdateingDataObj = null; private _updating: boolean = false; private _canRetry: boolean = false; private _isChangeUrl: boolean = false; private _path: string = "Bundle"; private _customManifest: string = ""; private _storagePath: string = ""; //#endregion //#region Lifecycle constructor(...params: any[]) { super(); if (!cc.sys.isNative) { return; } else if (params.length === 0) { return; } HUDM._instance = this; this.BundleName = params[0]; // let packageUrl: string = params[1]; // let BundleData: Enum_Loading.BundleDataObj = AssetBundleMamager.Instance.RemoteVerList[this.BundleName]; // let packageUrl: string = BundleData.BundleUrl; let packageUrl: string = `${BusinessTypeSetting.UsePatch}${BusinessTypeSetting.FolderUrlBundle}${this.BundleName}`; 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}/${this.BundleName}`; // 本地熱更目錄下已存在project.manifest,則直接修改已存在的project.manifest if (AssetBundleMamager.Instance.IsChangeBundleUrl) { 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); var vA: string[] = versionA.split("."); var vB: string[] = versionB.split("."); // 長度不相等,則進行更新 if (vA.length !== vB.length) { return -1; } for (var i: number = 0; i < vA.length; ++i) { var a: number = +vA[i]; var b: number = +vB[i] || 0; if (a === b) { // 數字相同,則跳過 continue; } else { // 數字不同,則進行更新 return -1; } } // 長度相等且數字相等,則不更新 return 0; }; this._initAssetManaget(); } private _initAssetManaget(): void { // 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. var compressed: any = asset.compressed; // Retrieve the correct md5 value. var expectedMD5: string = asset.md5; // asset.path is relative path and path is absolute. var relativePath: string = asset.path; // The size of asset file, but this value could be absent. var size: any = asset.size; if (compressed) { // panel.info.string = "Verification passed : " + relativePath; // cc.log("onLoad -> Verification passed : " + relativePath); return true; } else { // panel.info.string = "Verification passed : " + relativePath + ' (' + expectedMD5 + ')'; // cc.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.panel.info.string = "Max concurrent tasks count have been limited to 2"; // cc.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); let isWrittenProject: boolean = jsb.fileUtils.writeStringToFile(afterString, path); // // 更新數據庫中的新請求地址,下次如果檢測到不一致就重新修改 manifest 文件 // if (isWrittenProject) { // LocalStorageData.Instance.BundleUrl = BusinessTypeSetting.UsePatch; // } // console.log("[HUD] 修改是否成功,project.manifest:", isWrittenProject); // console.log("[HUD] 修改後文件:", projectManifestObj.packageUrl, projectManifestObj.remoteManifestUrl, projectManifestObj.remoteVersionUrl); } } } //#endregion public *CheckUpdate(): IterableIterator { this._needUpdateData = null; if (this._updating) { // this.panel.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}${BusinessTypeSetting.FolderUrlBundle}${this.BundleName}`; 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._needUpdateData.IsNeedUpdate || 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 { var failed: boolean = false; switch (event.getEventCode()) { case jsb.EventAssetsManager.ERROR_NO_LOCAL_MANIFEST: // this.tipsLabel.string = "No local manifest file found, HUD skipped."; 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: // this.tipsLabel.string = "Fail to download manifest file, HUD skipped."; console.error("checkCb -> Fail to download manifest file, HUD skipped."); failed = true; break; case jsb.EventAssetsManager.ALREADY_UP_TO_DATE: // this.tipsLabel.string = "Already up to date with the latest remote version."; // cc.log("checkCb -> Already up to date with the latest remote version."); this._needUpdateData = new Enum_Loading.NeedUpdateDataObj(false); break; case jsb.EventAssetsManager.NEW_VERSION_FOUND: // this.downloadLabel.node.active = true; // this.downloadLabel.string = "New version found, please try to update." + event.getTotalBytes(); // this.panel.checkBtn.active = false; // this.panel.fileProgress.progress = 0; // this.panel.byteProgress.progress = 0; // cc.log("checkCb -> New version found, please try to update." + event.getTotalBytes()); this._needUpdateData = new Enum_Loading.NeedUpdateDataObj(true, this._bytesToSize(event.getTotalBytes())); break; default: return; } this._am.setEventCallback(null); this._checkListener = null; this._updating = false; if (failed) { // } } public *HUD(onFileProgress?: (finish: number, total: number, item: string) => void): IterableIterator { 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_Loading.UpdateingDataObj(false); } } private _updateCb(event: jsb.EventAssetsManager): void { var needRestart: boolean = false; var failed: boolean = false; switch (event.getEventCode()) { case jsb.EventAssetsManager.ERROR_NO_LOCAL_MANIFEST: // this.panel.info.string = 'No local manifest file found, HUD skipped.'; cc.log("updateCb -> No local manifest file found, HUD skipped."); failed = true; break; case jsb.EventAssetsManager.UPDATE_PROGRESSION: // this.panel.byteProgress.progress = event.getPercent(); // this.panel.fileProgress.progress = event.getPercentByFile(); // this.panel.fileLabel.string = event.getDownloadedFiles() + ' / ' + event.getTotalFiles(); // this.tipsLabel.string = event.getDownloadedBytes() + " / " + event.getTotalBytes(); // cc.log("updateCb -> " + event.getDownloadedBytes() + " / " + event.getTotalBytes()); // var msg: string = event.getMessage(); // if (msg) { // // this.panel.info.string = 'Updated file: ' + msg; // cc.log("updateCb -> Updated file: " + msg); // console.log("updateCb -> " + event.getPercent() / 100 + "% : " + msg); // } var 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.panel.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.panel.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(); // cc.log("updateCb -> 更新完成. " + event.getMessage()); this._updateingData = new Enum_Loading.UpdateingDataObj(true); needRestart = true; break; case jsb.EventAssetsManager.UPDATE_FAILED: // this.panel.info.string = 'Update failed. ' + event.getMessage(); console.error("updateCb -> Update failed. " + event.getMessage()); // this.panel.retryBtn.active = true; this._canRetry = true; this._updateingData = new Enum_Loading.UpdateingDataObj(false); this._updating = false; break; case jsb.EventAssetsManager.ERROR_UPDATING: // this.panel.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.panel.info.string = event.getMessage(); console.error("updateCb -> " + event.getMessage()); break; default: break; } if (failed) { this._am.setEventCallback(null); this._updateListener = null; this._updating = false; } // 測試先不restart 之後看情況 // if (needRestart) { // this._am.setEventCallback(null); // this._updateListener = null; // // Prepend the manifest's search path // var searchPaths: string[] = jsb.fileUtils.getSearchPaths(); // // var newPaths = this._am.getLocalManifest().getSearchPaths(); // // cc.log("newPath."+JSON.stringify(newPaths)); // // Array.prototype.unshift.apply(searchPaths, newPaths); // cc.sys.localStorage.setItem("HUDSearchPaths", JSON.stringify(searchPaths)); // jsb.fileUtils.setSearchPaths(searchPaths); // cc.audioEngine.stopAll(); // cc.game.restart(); // } } public *RetryDownLoadFailedAssets(): IterableIterator { if (!this._updating && this._canRetry) { this._updateingData = null; // this.panel.retryBtn.active = false; this._canRetry = false; // this.panel.info.string = 'Retry failed Assets...'; // cc.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_Loading.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; } } }