366 lines
9.2 KiB
TypeScript
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);
|
|
}
|
|
}
|
|
}
|