449 lines
17 KiB
TypeScript
449 lines
17 KiB
TypeScript
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<any> {
|
||
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<any> {
|
||
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<any> = function* (): IterableIterator<any> {
|
||
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<any> {
|
||
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
|
||
}
|