import BusinessTypeSetting from "../../_BusinessTypeSetting/BusinessTypeSetting"; import { CoroutineV2 } from "../CatanEngine/CoroutineV2/CoroutineV2"; import LocalStorageData from "../Data/LocalStorageData"; import Enum_Loading from "./Enum_Loading"; export default class AssetBundleMamagerV2 { //#region static 屬性 private static _instance: AssetBundleMamagerV2 = null; public static get Instance(): AssetBundleMamagerV2 { return AssetBundleMamagerV2._instance; } //#endregion //#region public 屬性 /** 本地VerList */ public LocalVerList: Enum_Loading.VerListObj = null; /** 遠端VerList */ public RemoteVerList: JSON = null; public DownloadList_Preview: Object = {}; /** 快取資源 */ public CachedFiles: Map = new Map(); //#endregion //#region Lifecycle constructor() { AssetBundleMamagerV2._instance = this; CC_PREVIEW && this._initdownloadList_Preview(); } //#endregion //#region 清除資料 /** 判斷更改編譯版號.清除BUNDLE記錄 */ public CheckCompileVersion(): void { let oldCompileVersion: string = LocalStorageData.Instance.CompileVersion; let newCompileVersion: string = BusinessTypeSetting.COMPILE_VERSION; if (oldCompileVersion && oldCompileVersion !== newCompileVersion) { this.ClearBundleData(); console.log("change compile version."); } 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.log("change patch url."); } LocalStorageData.Instance.BundleUrl = BusinessTypeSetting.UsePatch; } /** 清除Bundle資料 */ public ClearBundleData(): void { cc.sys.localStorage.removeItem("LocalVerList"); cc.sys.localStorage.removeItem("RemoteVerList"); cc.assetManager.bundles.clear(); if (CC_JSB) { cc.assetManager.cacheManager.clearCache(); console.log("clear bundle data."); } } /** 清除所有資料重啟 */ public ClearAppDataToRestart(): void { cc.sys.localStorage.clear(); cc.assetManager.bundles.clear(); if (CC_JSB) { cc.assetManager.cacheManager.clearCache(); cc.game.restart(); } else { window.location.reload(); } } //#endregion //#region Custom Function /** * 取得Bundle * @param {string} BundleName Bundle名稱 * @param {string} Version 版號 * @return {cc.AssetManager.Bundle} Bundle */ public *GetBundle(BundleName: string): IterableIterator { let self: this = this; let bundle: cc.AssetManager.Bundle | boolean = cc.assetManager.getBundle(BundleName); if (bundle) { yield* this.GetDepsBundle(bundle.deps); return bundle; } /** 判斷是不是要下載新版本 */ let isNeedUpdate: boolean = this.IsNeedUpdate(BundleName); if (isNeedUpdate) { // 下載新版本前需要先清除暫存 console.log(`removeCache: ${BundleName}`); this.DelBundleCache(BundleName); this.LocalVerList[BundleName].UseLocal = false; } /** Bundle路徑 */ let BundleUrl: string = BusinessTypeSetting.GetRemoteFileUrl(BundleName); if (CC_DEV) { // CC_DEVBundle路徑為: BundleName // if (BundleName.indexOf("Script") != -1) { BundleUrl = `${BundleName}`; // } else { // BundleUrl = "http://192.168.7.57/bj_casino/test/" + BundleName; // } } else if (this.LocalVerList[BundleName].UseLocal) { // 本地Bundle路徑為: assets/assets/${BundleName} BundleUrl = `assets/${BundleName}`; } if (CC_DEV) { cc.assetManager.loadBundle(BundleUrl, (err: Error, resp: cc.AssetManager.Bundle) => { if (err) { console.error(err); bundle = null; return; } bundle = resp; }); while (typeof bundle === "undefined") { yield null; } } else if (BundleName.includes("Script")) { bundle = yield* self.loadScriptBundle(BundleUrl); } else { if (CC_JSB && !this.LocalVerList[BundleName].UseLocal) { let bundlePath: string = `${jsb.fileUtils.getWritablePath()}gamecaches/${BundleName}/`; if (!jsb.fileUtils.isFileExist(bundlePath)) { cc.assetManager.cacheManager["makeBundleFolder"](BundleName); } } bundle = yield* self.loadUIBundle(BundleUrl); } if (bundle) { yield* this.GetDepsBundle((bundle).deps); if (isNeedUpdate) { // 下載成功後更改本地Bundle版本 self.LocalVerList[BundleName].Version = self.RemoteVerList[BundleName]; LocalStorageData.Instance.LocalVerList = JSON.stringify(self.LocalVerList); } } return bundle; } /** * 從Bundle取得資源 * @param {string} BundleUrl Bundle路徑 */ public *loadScriptBundle(BundleUrl: string): IterableIterator { let fileName: string = `index.${CC_DEBUG ? "js" : "jsc"}`; let fileUrl: string = `${BundleUrl}/${fileName}`; let run: boolean = true; let isSuceess: boolean = false; cc.assetManager.loadScript(fileUrl, (err: Error) => { if (err) { console.error(`[Error] ${fileUrl}載入失敗 err: ${err}`); run = false; return; } isSuceess = true; run = false; }); while (run) { yield null; } return isSuceess; } /** * 從Bundle取得資源 * @param {string} BundleUrl Bundle路徑 */ public *loadUIBundle(BundleUrl: string): IterableIterator { let fileName: string = "config.json"; let fileUrl: string = `${BundleUrl}/${fileName}`; let data: any; let run: boolean = true; cc.assetManager.loadRemote(fileUrl, (err: Error, res: cc.JsonAsset) => { if (err) { console.error(`[Error] ${fileUrl}載入失敗 err: ${err}`); return; } data = res.json; run = false; }); while (run) { yield null; } let bundle: cc.AssetManager.Bundle = new cc.AssetManager.Bundle(); data.base = `${BundleUrl}/`; bundle.init(data); return bundle; } /** * 取得Bundle * @param {string} BundleName Bundle名稱 * @param {string} Version 版號 * @return {cc.AssetManager.Bundle} Bundle */ public *GetDepsBundle(deps: string[]): IterableIterator { if (!deps || deps.length <= 2) { return; } let self: this = this; let GetBundle_F_Arr: IterableIterator[] = []; for (const bundleName of deps) { if (!["main", "internal"].includes(bundleName)) { let GetBundle_F: IterableIterator = function* (): IterableIterator { yield* self.GetBundle(bundleName); }(); GetBundle_F_Arr.push(GetBundle_F); } } yield CoroutineV2.Parallel(...GetBundle_F_Arr).Start(); } /** * 從Bundle取得資源 * @param {number} slotID slotID * @param {Function} onFileProgress onFileProgress */ public *PreloadBundleScene(slotID: number, onFileProgress?: (finish: number, total: number, item: cc.AssetManager.RequestItem) => void): IterableIterator { let BundleName: string = `Game_${slotID}`; let SourceName: string = `Slot${slotID}`; let run: boolean = true; let UpdateingData: Enum_Loading.UpdateingDataObj = new Enum_Loading.UpdateingDataObj(false); let bundle: cc.AssetManager.Bundle = yield* AssetBundleMamagerV2.Instance.GetBundle(BundleName); if (!bundle) { console.error(`GetBundleSource Error BundleName: ${BundleName}`); return UpdateingData; } bundle.preloadScene(SourceName, onFileProgress, function (error: Error): void { if (error) { console.error(error); run = false; return; } UpdateingData.IsUpdatecomplete = true; run = false; }); while (run) { yield null; } 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 = yield* AssetBundleMamagerV2.Instance.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} bundleName bundleName * @param {string} sourcePath Bundle資料夾下的路徑 */ public static GetBundleSourceV2(bundleName: cc.bundleName | string, sourcePath: string): cc.Prefab { let bundle: cc.AssetManager.Bundle = cc.assetManager.getBundle(bundleName); if (!bundle) { cc.error(`GetBundleSourceV2 getBundle error bundleName: ${bundleName}`); return null; } let source: cc.Prefab = bundle.get(sourcePath, cc.Prefab); if (!source) { cc.error(`GetBundleSourceV2 bundle.get error bundleName: ${bundleName}, sourcePath: ${sourcePath}`); return 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; } this.ReleaseSlotCache(slotID); } /** * 從cachedFiles刪除暫存資源 * @param {string} BundleName Bundle名稱 */ public DelBundleCache(BundleName: string): void { if (CC_BUILD && cc.sys.isNative) { let cachedFiles: Object = cc.assetManager.cacheManager.cachedFiles["_map"]; let delcache: string = BusinessTypeSetting.GetRemoteFileUrl(BundleName) + "/"; for (let cached of Object.keys(cachedFiles)) { if (cached.includes(delcache)) { cc.assetManager.cacheManager.removeCache(cached); // console.log(`removeCache: ${cached}`); } } } } /** * 從cachedFiles釋放暫存資源 * @param {number} slotID slotID */ public ReleaseSlotCache(slotID: number): void { if (!CC_JSB) { return; } let delcachedKeys: string[] = []; let cachedFiles: Map = this.CachedFiles; let delcache_group: string[] = [`shared/jsons`, `Slot/Slot${slotID}`, "sounds/Slot/Default", "submit.txt"]; cachedFiles.forEach((cached: cc.Asset, key: string, map: Map) => { for (var i: number = 0; i < delcache_group.length; ++i) { let delcache: string = delcache_group[i]; if (key.includes(delcache)) { cc.assetManager.releaseAsset(cached); delcachedKeys.push(key); break; } } }); for (var i: number = 0; i < delcachedKeys.length; ++i) { this.CachedFiles.delete(delcachedKeys[i]); } } /** * 判斷要不要更新 * @param {string} BundleName Bundle名稱 */ public IsNeedUpdate(BundleName: string): boolean { let isNeedUpdate: boolean; // 判斷本地有無Bundle資料 if (this.LocalVerList[BundleName] && !this.LocalVerList[BundleName].HasBundle) { if (this.RemoteVerList[BundleName]) { // 改成有包過Bundle了,重新走下面流程 this.LocalVerList[BundleName] = null; } else { return true; } } if (!this.LocalVerList[BundleName]) { // 本地無Bundle資料需要新增 this.LocalVerList[BundleName] = new Enum_Loading.BundleDataObj(); LocalStorageData.Instance.LocalVerList = JSON.stringify(this.LocalVerList); } let version: string = this.RemoteVerList[BundleName]; if (!version) { // !version代表還沒包Bundle this.LocalVerList[BundleName].HasBundle = false; LocalStorageData.Instance.LocalVerList = JSON.stringify(this.LocalVerList); return true; } else if (version === "0") { // version === "0" 代表要使用本體Bundle this.LocalVerList[BundleName].UseLocal = true; this.LocalVerList[BundleName].Version = "0"; LocalStorageData.Instance.LocalVerList = JSON.stringify(this.LocalVerList); } isNeedUpdate = AssetBundleMamagerV2.Instance.versionCompareHandle(this.LocalVerList[BundleName].Version, this.RemoteVerList[BundleName]) !== 0 ? true : false; return isNeedUpdate; } /** * 比對版號(熱更能從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 a - b; } } // 長度相等且數字相等,則不更新 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 : {}; } public 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