import BusinessTypeSetting from "../../_BusinessTypeSetting/BusinessTypeSetting"; import LocalStorageData from "../Data/LocalStorageData"; import Enum_Loading from "../HUDV2/Enum_Loading"; import HUDM from "./HUDM"; export default class AssetBundleMamager { //#region static 屬性 private static _instance: AssetBundleMamager = null; public static get Instance(): AssetBundleMamager { return AssetBundleMamager._instance; } //#endregion //#region public 屬性 public HUGroup: Map = new Map(); /** 本地VerList */ public LocalVerList: Enum_Loading.VerListObj = null; /** 遠端VerList */ public RemoteVerList: Enum_Loading.VerListObj = null; public DownloadList_Preview: Object = {}; /** IsChangeBundleUrl */ public IsChangeBundleUrl: boolean = false; //#endregion //#region Lifecycle constructor() { AssetBundleMamager._instance = this; CC_PREVIEW && this._initdownloadList_Preview(); } //#endregion //#region Custom Function /** * 取得Bundle * @param {string} BundleName Bundle名稱 * @param {string} Version 版號 * @return {cc.AssetManager.Bundle} Bundle */ public *GetBundle(BundleName: string, Version: string = ""): IterableIterator { let bundle: cc.AssetManager.Bundle = cc.assetManager.getBundle(BundleName); if (bundle) { return bundle; } // options是可选参数,引擎会根据保留字段 进行对应的操作,这里添加了version和onFileProgress,可用来记录热更资源版本和下载进度 let options: any = null; let BundleUrl: string = BundleName; if (cc.sys.isNative && !this.LocalVerList[BundleName].UseLocal) { BundleUrl = `${(jsb.fileUtils ? jsb.fileUtils.getWritablePath() : "/")}Bundle/${BundleName}/remote/${BundleName}`; options = { version: Version }; } cc.assetManager.loadBundle(BundleUrl, options, (err: Error, resp: cc.AssetManager.Bundle) => { if (err) { cc.error(err); bundle = null; } bundle = resp; }); while (typeof bundle === "undefined") { yield null; } return bundle; } /** * 更新Bundle * @param {HUDM} HUDName HUD */ public *UpdateBundle(HUDName: HUDM | string, onFileProgress?: (finish: number, total: number, item: string) => void): IterableIterator { let HUD: HUDM; if (HUDName instanceof HUDM) { HUD = HUDName; } else { HUD = this.GetHUD(HUDName); } let UpdateingData: Enum_Loading.UpdateingDataObj = yield* HUD.HUD(onFileProgress); if (UpdateingData.IsUpdatecomplete) { this.LocalVerList[HUD.BundleName] = this.RemoteVerList[HUD.BundleName]; this.LocalVerList[HUD.BundleName]["UseLocal"] = false; LocalStorageData.Instance.LocalVerList = JSON.stringify(this.LocalVerList); } return UpdateingData; } /** * 更新Bundle * @param {HUDM} HUDName HUD */ public *RetryUpdateBundle(HUDName: HUDM | string, onFileProgress?: (finish: number, total: number, item: string) => void): IterableIterator { let HUD: HUDM; if (HUDName instanceof HUDM) { HUD = HUDName; } else { HUD = this.GetHUD(HUDName); } let UpdateingData: Enum_Loading.UpdateingDataObj = yield* HUD.RetryDownLoadFailedAssets(); return UpdateingData; } /** * 從Bundle取得資源 * @param {cc.AssetManager.Bundle | string} BundleName Bundle名稱 * @param {string} SourceName 資源名稱 * @param {string} type 資源型別 * @return {any} Source */ public *GetBundleSource(BundleName: cc.AssetManager.Bundle | string, SourceName: string, type?: string | Bundle_Source_Type, onFileProgress?: (finish: number, total: number, item: cc.AssetManager.RequestItem) => void): IterableIterator { let bundle: cc.AssetManager.Bundle; let source: any; if (BundleName instanceof cc.AssetManager.Bundle) { bundle = BundleName; } else { bundle = cc.assetManager.getBundle(BundleName); if (!bundle) { cc.error(`GetBundleSource Error BundleName: ${BundleName}`); return null; } } switch (type) { case Bundle_Source_Type.Scene: { bundle.loadScene(SourceName, onFileProgress, function (err: Error, scene: cc.SceneAsset): void { if (err) { cc.error(err); return null; } // cc.director.runScene(scene); source = scene; }); break; } case Bundle_Source_Type.Json: { bundle.load(SourceName, onFileProgress, function (err: Error, json: cc.JsonAsset): void { if (err) { cc.error(err); return null; } // source = JSON.parse(json["_nativeAsset"]); source = json; }); break; } case Bundle_Source_Type.Prefab: { bundle.load(SourceName, cc.Prefab, onFileProgress, function (err: Error, prefab: cc.Asset): void { if (err) { cc.error(err); return null; } // source = JSON.parse(json["_nativeAsset"]); source = prefab; }); break; } default: bundle.load(SourceName, function (err: Error, any: any): void { if (err) { cc.error(err); return null; } source = any; }); break; } while (typeof source === "undefined") { yield null; } return source; } /** * 釋放Bundle * @param {string} slotID slotID */ public *BundleRelease(slotID: number): IterableIterator { let gameName: string = `Game_${slotID}`; let sceneName: string = `Slot${slotID}`; let bundle: cc.AssetManager.Bundle = cc.assetManager.getBundle(gameName); if (!bundle) { cc.log(`BundleRelease Error BundleName: ${gameName}`); return; } // let bundles: cc.AssetManager.Cache = cc.assetManager.bundles; // let cacheDir: string = cc.assetManager.cacheManager.cacheDir; // let cachedFiles: Object = cc.assetManager.cacheManager.cachedFiles; yield* this.DelBundleCache(bundle); yield* this.DelOthersCache(slotID); bundle.release(sceneName, cc.SceneAsset); cc.assetManager.removeBundle(bundle); cc.sys.garbageCollect(); } /** * 從Bundle刪除暫存資源 * @param {string} BundleName Bundle名稱 */ public *DelBundleCache(BundleName: cc.AssetManager.Bundle | string): IterableIterator { if (!CC_JSB) { return; } let bundle: cc.AssetManager.Bundle; let source: any; if (BundleName instanceof cc.AssetManager.Bundle) { bundle = BundleName; } else { bundle = cc.assetManager.getBundle(BundleName); if (!bundle) { // cc.error(`GetBundleSource Error BundleName: ${BundleName}`); // return; bundle = yield* AssetBundleMamager.Instance.GetBundle(BundleName, this.RemoteVerList[BundleName].Version); } } let _map: Object = bundle["_config"].assetInfos._map; for (let map of Object.keys(_map)) { let path: string = _map[map].path; if (!path) { break; } source = yield* AssetBundleMamager.Instance.GetBundleSource(bundle, path); cc.assetManager.cacheManager.removeCache(source.nativeUrl); bundle.release(path); // return; } } /** * 從cachedFiles刪除暫存資源 * @param {number} slotID slotID */ public *DelOthersCache(slotID: number): IterableIterator { if (!CC_JSB) { return; } let cachedFiles: Object = cc.assetManager.cacheManager.cachedFiles["_map"]; let delcache_group: string[] = [`shared/jsons`, `Slot/Slot${slotID}`, "sounds/Slot/Default", `${BusinessTypeSetting.FolderUrlBundle}project.manifest`, "submit.txt"]; for (let cached of Object.keys(cachedFiles)) { for (var i: number = 0; i < delcache_group.length; ++i) { let delcache: string = delcache_group[i]; if (cached.includes(delcache)) { cc.assetManager.cacheManager.removeCache(cached); // console.log(`removeCache: ${cached}`); break; } } } } public GetHUD(BundleName: HUDM | string): HUDM { let HUD: HUDM; if (BundleName instanceof HUDM) { HUD = BundleName; } else { if (!this.HUGroup.has(BundleName)) { HUD = new HUDM(BundleName); this.HUGroup.set(BundleName, HUD); } else { HUD = this.HUGroup.get(BundleName); } HUD = this.HUGroup.get(BundleName); } return HUD; } /** 刪除全部暫存資源 */ public ClearAllCache(): void { cc.assetManager.cacheManager.clearCache(); cc.game.restart(); } public *CheckBundleNeedHUD(BundleName: HUDM | string): IterableIterator { let HUD: HUDM; if (BundleName instanceof HUDM) { HUD = BundleName; } else { HUD = this.GetHUD(BundleName); } if (!this.LocalVerList[HUD.BundleName]) { this.LocalVerList[HUD.BundleName] = new Enum_Loading.BundleDataObj(); let apkVersion: string = this.RemoteVerList[HUD.BundleName].ApkVersion; if (apkVersion && apkVersion !== "0") { this.LocalVerList[HUD.BundleName].UseLocal = true; this.LocalVerList[HUD.BundleName].Version = apkVersion; } LocalStorageData.Instance.LocalVerList = JSON.stringify(this.LocalVerList); } else { if (this.RemoteVerList[HUD.BundleName].Version === this.RemoteVerList[HUD.BundleName].ApkVersion) { this.LocalVerList[HUD.BundleName] = this.RemoteVerList[HUD.BundleName]; this.LocalVerList[HUD.BundleName].UseLocal = true; } } let UpdateData: Enum_Loading.NeedUpdateDataObj = new Enum_Loading.NeedUpdateDataObj(); if (this.LocalVerList[HUD.BundleName].UseLocal) { UpdateData.IsNeedUpdate = AssetBundleMamager.Instance.versionCompareHandle(this.LocalVerList[HUD.BundleName].Version, this.RemoteVerList[HUD.BundleName].Version) < 0 ? true : false; if (UpdateData.IsNeedUpdate) { UpdateData = yield* HUD.CheckUpdate(); } } else { UpdateData = yield* HUD.CheckUpdate(); } return UpdateData; } // public *CheckBundleNeedHUD(BundleName: string): IterableIterator { // if (!this.LocalVerList[BundleName]) { // this.LocalVerList[BundleName] = new Enum_Loading.BundleDataObj(); // let apkVersion: string = this.RemoteVerList[BundleName].ApkVersion; // if (apkVersion && apkVersion !== "0") { // this.LocalVerList[BundleName].UseLocal = true; // this.LocalVerList[BundleName].Version = apkVersion; // } // LocalStorageData.Instance.LocalVerList = JSON.stringify(this.LocalVerList); // } // let IsUpdate: boolean = AssetBundleMamager.Instance.versionCompareHandle(this.LocalVerList[BundleName].Version, this.RemoteVerList[BundleName].Version) < 0 ? true : false; // return IsUpdate; // } public CheckGameNeedUpdate(GameID: number): boolean { let IsUpdate: boolean = false; let bundleName: string = `Game_${GameID}`; if (!this.RemoteVerList[bundleName]) { this.RemoteVerList[bundleName] = new Enum_Loading.BundleDataObj(); this.RemoteVerList[bundleName].HasBundle = false; LocalStorageData.Instance.RemoteVerList = JSON.stringify(this.RemoteVerList); IsUpdate = true; } if (!this.LocalVerList[bundleName]) { this.LocalVerList[bundleName] = new Enum_Loading.BundleDataObj(); let apkVersion: string = this.RemoteVerList[bundleName].ApkVersion; if (apkVersion && apkVersion !== "0") { this.LocalVerList[bundleName].UseLocal = true; this.LocalVerList[bundleName].Version = apkVersion; } LocalStorageData.Instance.LocalVerList = JSON.stringify(this.LocalVerList); } if (CC_PREVIEW) { return this._getIsDownload_Preview(GameID); } if (IsUpdate) { return IsUpdate; } IsUpdate = AssetBundleMamager.Instance.versionCompareHandle(this.LocalVerList[bundleName].Version, this.RemoteVerList[bundleName].Version) < 0 ? true : false; return IsUpdate; } /** * 比對版號(熱更能從1.0.0更新到2.0.0,從2.0.0回退到1.0.0) * 官方提供的版本比較函數,只有服務端版本>客戶端版本時,才會進行更新。所以不能從2.0.0回退到1.0.0版本。 * @param {string} versionA 本地版號 * @param {string} versionB 遠程版號 * @return {number} num = -1 須更新 * @return {number} num = 0 不須更新 */ public versionCompareHandle(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; } //#endregion //#region DownloadList_Preview private _initdownloadList_Preview(): void { this.DownloadList_Preview = JSON.parse(LocalStorageData.Instance.DownloadList_Preview); this.DownloadList_Preview = this.DownloadList_Preview ? this.DownloadList_Preview : {}; } private _getIsDownload_Preview(slotID: number): boolean { if (!this.DownloadList_Preview[slotID]) { this.SetIsDownload_Preview(slotID, false); } return !this.DownloadList_Preview[slotID]; } public SetIsDownload_Preview(slotID: number, isDownload: boolean = true): void { this.DownloadList_Preview[slotID] = isDownload; LocalStorageData.Instance.DownloadList_Preview = JSON.stringify(this.DownloadList_Preview); } //#endregion } //#region enum /** Bundle資源類型 */ export enum Bundle_Source_Type { /** Json */ Json = "json", /** Scene */ Scene = "scene", /** Prefab */ Prefab = "prefab" } //#endregion //#region 廢棄 Function // /** // * 從Bundle刪除暫存資源 // * @param {string} BundleName Bundle名稱 // */ // public *DelBundleCache(BundleName: cc.AssetManager.Bundle | string): IterableIterator { // if (!CC_JSB) { // return; // } // let WritablePath: string = `${jsb.fileUtils.getWritablePath()}gamecaches/${BundleName}`; // if (jsb.fileUtils.isDirectoryExist(WritablePath)) { // jsb.fileUtils.removeDirectory(WritablePath); // } // } //#endregion