2022-05-26 11:46:36 +08:00

366 lines
9.2 KiB
TypeScript

import { isTest } from '../Config';
import { GIFCache } from '../gif/GIF';
import { getSwitch } from '../jcfw/service/jclogin';
const { ccclass, property } = cc._decorator;
const UpdateMsg = [
'No use hot update!',
'No local manifest file found, hot update skipped.',
'Fail to download manifest file, hot update skipped.',
'Fail to parse manifest file, hot update skipped.',
'Asset update error.',
'Compression package decoding failed.',
'Already up to date with the latest remote version.',
'New version found, please try to update.',
'Update finished.',
'Update failed.',
];
enum State {
NONE,
ERROR_NO_LOCAL_MANIFEST,
ERROR_DOWNLOAD_MANIFEST,
ERROR_PARSE_MANIFEST,
ERROR_UPDATING,
ERROR_DECOMPRESS,
ALREADY_UP_TO_DATE,
NEW_VERSION_FOUND,
UPDATE_FINISHED,
UPDATE_FAILED,
UPDATE_PROGRESSION,
}
@ccclass
export default class HotUpdate extends cc.Component {
static State = State;
@property(cc.Node) loginNode: cc.Node = null;
@property({
type: cc.Asset,
tooltip: 'current manifest',
})
manifestUrl: cc.Asset = null;
@property(cc.ProgressBar) bar: cc.ProgressBar = null;
@property(cc.Label) log: cc.Label = null;
@property(cc.Label) currentProcess: cc.Label = null; //
state = State.NONE;
private _updating = false;
private _canRetry = false;
private _am = null;
private _failCount = 0;
private _totalFilesCount = 0;
private _totalBytesCount = 0;
// use this for initialization
init() {
return new Promise((resolve, reject) => {
// Hot update is only available in Native build
if (!cc.sys.isNative) {
return;
}
let storagePath =
(jsb.fileUtils ? jsb.fileUtils.getWritablePath() : '/') +
'blackjack-remote-asset';
console.log('Storage path for remote asset : ' + storagePath);
// Setup your own version compare handler, versionA and B is versions in string
// if the return value greater than 0, versionA is greater than B,
// if the return value equals 0, versionA equals to B,
// if the return value smaller than 0, versionA is smaller than B.
let versionCompareHandle = (versionA, versionB) => {
console.log(
'JS Custom Version Compare: version A is ' +
versionA +
', version B is ' +
versionB
);
let vA = versionA.split('.');
let vB = versionB.split('.');
for (let i = 0; i < vA.length; ++i) {
let a = parseInt(vA[i]);
let b = parseInt(vB[i] || 0);
if (a === b) {
continue;
} else {
return a - b;
}
}
if (vB.length > vA.length) {
return -1;
} else {
return 0;
}
};
// Init with empty manifest url for testing custom manifest
this._am = new jsb.AssetsManager(
'',
storagePath,
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, asset) {
// When asset is compressed, we don't need to check its md5, because zip file have been deleted.
let compressed = asset.compressed;
// Retrieve the correct md5 value.
let expectedMD5 = asset.md5;
// asset.path is relative path and path is absolute.
let relativePath = asset.path;
// The size of asset file, but this value could be absent.
let size = asset.size;
if (compressed) {
console.log('Verification passed : ' + relativePath);
return true;
} else {
console.log(
'Verification passed : ' +
relativePath +
' (' +
expectedMD5 +
')'
);
return true;
}
});
// 'Hot update is ready, please check or directly update.';
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.
// Max concurrent tasks count have been limited to 2
this._am.setMaxConcurrentTask(2);
}
// this.checkUpdate();
resolve(true);
});
}
/**
*/
async checkUpdate() {
this.log.string = 'checking...';
return new Promise((resolve, reject) => {
if (!cc.sys.isNative) {
return;
}
if (this._updating) {
return;
}
if (this._am.getState() === jsb.AssetsManager.State.UNINITED) {
// Resolve md5 url
let url = this.manifestUrl.nativeUrl;
if (cc.loader.md5Pipe) {
url = cc.loader.md5Pipe.transformURL(url);
}
this._am.loadLocalManifest(url);
}
if (
!this._am.getLocalManifest() ||
!this._am.getLocalManifest().isLoaded()
) {
return;
}
this._am.setEventCallback(this.checkUpdateCallback.bind(this));
this._am.checkUpdate();
this._updating = true;
});
}
/**
*/
hotUpdate() {
// this.uiloading.active = true;
this.log.string = 'updating...';
if (!cc.sys.isNative) {
return;
}
if (this._am && !this._updating) {
this._am.setEventCallback(this.hotUpdateCallback.bind(this));
if (this._am.getState() === jsb.AssetsManager.State.UNINITED) {
// Resolve md5 url
let url = this.manifestUrl.nativeUrl;
if (cc.loader.md5Pipe) {
url = cc.loader.md5Pipe.transformURL(url);
}
this._am.loadLocalManifest(url);
}
this._failCount = 0;
this._am.update();
this._updating = true;
}
}
retry() {
if (!cc.sys.isNative) {
return;
}
if (!this._updating && this._canRetry) {
this._canRetry = false;
// 'Retry failed Assets...';
this._am.downloadFailedAssets();
}
}
private checkUpdateCallback(event) {
let logMsg = '';
switch (event.getEventCode()) {
case jsb.EventAssetsManager.ERROR_NO_LOCAL_MANIFEST:
this.state = State.ERROR_NO_LOCAL_MANIFEST;
break;
case jsb.EventAssetsManager.ERROR_DOWNLOAD_MANIFEST:
this.state = State.ERROR_DOWNLOAD_MANIFEST;
break;
case jsb.EventAssetsManager.ERROR_PARSE_MANIFEST:
this.state = State.ERROR_PARSE_MANIFEST;
break;
case jsb.EventAssetsManager.ALREADY_UP_TO_DATE:
this.state = State.ALREADY_UP_TO_DATE;
this.loginNode.active = true;
this.node.destroy();
break;
case jsb.EventAssetsManager.NEW_VERSION_FOUND:
this.state = State.NEW_VERSION_FOUND;
// update
this.scheduleOnce(() => {
this.hotUpdate();
}, 1);
break;
default:
return;
}
this._am.setEventCallback(null);
this._updating = false;
logMsg = UpdateMsg[this.state];
}
private hotUpdateCallback(event) {
let needRestart = false;
let failed = false;
switch (event.getEventCode()) {
case jsb.EventAssetsManager.ERROR_NO_LOCAL_MANIFEST:
this.state = State.ERROR_NO_LOCAL_MANIFEST;
failed = true;
break;
case jsb.EventAssetsManager.UPDATE_PROGRESSION:
this.state = State.UPDATE_PROGRESSION;
this._totalFilesCount = event.getTotalFiles();
this._totalBytesCount = event.getTotalBytes();
this.bar.progress = event.getPercent();
this.currentProcess.string = `${Math.round(
event.getPercent() * 100
).toString()}%`;
//
break;
case jsb.EventAssetsManager.ERROR_DOWNLOAD_MANIFEST:
this.state = State.ERROR_DOWNLOAD_MANIFEST;
break;
case jsb.EventAssetsManager.ERROR_PARSE_MANIFEST:
this.state = State.ERROR_PARSE_MANIFEST;
failed = true;
break;
case jsb.EventAssetsManager.ALREADY_UP_TO_DATE:
this.state = State.ALREADY_UP_TO_DATE;
failed = true;
break;
case jsb.EventAssetsManager.UPDATE_FINISHED:
this.state = State.UPDATE_FINISHED;
needRestart = true;
break;
case jsb.EventAssetsManager.UPDATE_FAILED:
this.state = State.UPDATE_FAILED;
this._updating = false;
this._canRetry = true;
break;
case jsb.EventAssetsManager.ERROR_UPDATING:
this.state = State.ERROR_UPDATING;
this._failCount++;
break;
case jsb.EventAssetsManager.ERROR_DECOMPRESS:
this.state = State.ERROR_DECOMPRESS;
// logMsg = event.getMessage();
break;
default:
break;
}
if (failed) {
this._am.setEventCallback(null);
this._updating = false;
}
if (needRestart) {
this._am.setEventCallback(null);
// Prepend the manifest's search path
let searchPaths = jsb.fileUtils.getSearchPaths();
let newPaths = this._am.getLocalManifest().getSearchPaths();
console.log('new search path:', 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);
// this.uiloading.active = false;
console.log('hotupdate done!');
this.log.string = 'finish';
// this.scheduleOnce(() => {
// cc.game.restart();
// }, 1);
setTimeout(() => {
cc.game.restart();
}, 1000);
}
}
protected onLoad(): void {
// GIFCache.getInstance();
getSwitch(() => {
if (cc.sys.isNative && !isTest) {
this.init().then(async () => {
await this.checkUpdate();
});
} else {
this.loginNode.active = true;
this.node.destroy();
}
});
}
onDestroy() {
if (cc.sys.isNative && this._am) {
this._am.setEventCallback(null);
}
}
}