522 lines
16 KiB
TypeScript
522 lines
16 KiB
TypeScript
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<string, cc.Asset> = new Map<string, cc.Asset>();
|
||
|
||
//#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<cc.AssetManager.Bundle> {
|
||
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((<cc.AssetManager.Bundle>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<any> {
|
||
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<cc.AssetManager.Bundle> {
|
||
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<any> {
|
||
if (!deps || deps.length <= 2) {
|
||
return;
|
||
}
|
||
let self: this = this;
|
||
let GetBundle_F_Arr: IterableIterator<any>[] = [];
|
||
for (const bundleName of deps) {
|
||
if (!["main", "internal"].includes(bundleName)) {
|
||
let GetBundle_F: IterableIterator<any> = function* (): IterableIterator<any> {
|
||
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<any> {
|
||
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<any> {
|
||
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<any> {
|
||
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<string, cc.Asset> = 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<string, cc.Asset>) => {
|
||
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
|