一个Unity的WebGL的资源更新逻辑
Posted stalendp
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一个Unity的WebGL的资源更新逻辑相关的知识,希望对你有一定的参考价值。
直接贴代码了
// md5 function
!function (n) "use strict"; function t(n, t) var r = (65535 & n) + (65535 & t); return (n >> 16) + (t >> 16) + (r >> 16) << 16 | 65535 & r function r(n, t) return n << t | n >>> 32 - t function e(n, e, o, u, c, f) return t(r(t(t(e, n), t(u, f)), c), o) function o(n, t, r, o, u, c, f) return e(t & r | ~t & o, n, t, u, c, f) function u(n, t, r, o, u, c, f) return e(t & o | r & ~o, n, t, u, c, f) function c(n, t, r, o, u, c, f) return e(t ^ r ^ o, n, t, u, c, f) function f(n, t, r, o, u, c, f) return e(r ^ (t | ~o), n, t, u, c, f) function i(n, r) n[r >> 5] |= 128 << r % 32, n[14 + (r + 64 >>> 9 << 4)] = r; var e, i, a, d, h, l = 1732584193, g = -271733879, v = -1732584194, m = 271733878; for (e = 0; e < n.length; e += 16)i = l, a = g, d = v, h = m, g = f(g = f(g = f(g = f(g = c(g = c(g = c(g = c(g = u(g = u(g = u(g = u(g = o(g = o(g = o(g = o(g, v = o(v, m = o(m, l = o(l, g, v, m, n[e], 7, -680876936), g, v, n[e + 1], 12, -389564586), l, g, n[e + 2], 17, 606105819), m, l, n[e + 3], 22, -1044525330), v = o(v, m = o(m, l = o(l, g, v, m, n[e + 4], 7, -176418897), g, v, n[e + 5], 12, 1200080426), l, g, n[e + 6], 17, -1473231341), m, l, n[e + 7], 22, -45705983), v = o(v, m = o(m, l = o(l, g, v, m, n[e + 8], 7, 1770035416), g, v, n[e + 9], 12, -1958414417), l, g, n[e + 10], 17, -42063), m, l, n[e + 11], 22, -1990404162), v = o(v, m = o(m, l = o(l, g, v, m, n[e + 12], 7, 1804603682), g, v, n[e + 13], 12, -40341101), l, g, n[e + 14], 17, -1502002290), m, l, n[e + 15], 22, 1236535329), v = u(v, m = u(m, l = u(l, g, v, m, n[e + 1], 5, -165796510), g, v, n[e + 6], 9, -1069501632), l, g, n[e + 11], 14, 643717713), m, l, n[e], 20, -373897302), v = u(v, m = u(m, l = u(l, g, v, m, n[e + 5], 5, -701558691), g, v, n[e + 10], 9, 38016083), l, g, n[e + 15], 14, -660478335), m, l, n[e + 4], 20, -405537848), v = u(v, m = u(m, l = u(l, g, v, m, n[e + 9], 5, 568446438), g, v, n[e + 14], 9, -1019803690), l, g, n[e + 3], 14, -187363961), m, l, n[e + 8], 20, 1163531501), v = u(v, m = u(m, l = u(l, g, v, m, n[e + 13], 5, -1444681467), g, v, n[e + 2], 9, -51403784), l, g, n[e + 7], 14, 1735328473), m, l, n[e + 12], 20, -1926607734), v = c(v, m = c(m, l = c(l, g, v, m, n[e + 5], 4, -378558), g, v, n[e + 8], 11, -2022574463), l, g, n[e + 11], 16, 1839030562), m, l, n[e + 14], 23, -35309556), v = c(v, m = c(m, l = c(l, g, v, m, n[e + 1], 4, -1530992060), g, v, n[e + 4], 11, 1272893353), l, g, n[e + 7], 16, -155497632), m, l, n[e + 10], 23, -1094730640), v = c(v, m = c(m, l = c(l, g, v, m, n[e + 13], 4, 681279174), g, v, n[e], 11, -358537222), l, g, n[e + 3], 16, -722521979), m, l, n[e + 6], 23, 76029189), v = c(v, m = c(m, l = c(l, g, v, m, n[e + 9], 4, -640364487), g, v, n[e + 12], 11, -421815835), l, g, n[e + 15], 16, 530742520), m, l, n[e + 2], 23, -995338651), v = f(v, m = f(m, l = f(l, g, v, m, n[e], 6, -198630844), g, v, n[e + 7], 10, 1126891415), l, g, n[e + 14], 15, -1416354905), m, l, n[e + 5], 21, -57434055), v = f(v, m = f(m, l = f(l, g, v, m, n[e + 12], 6, 1700485571), g, v, n[e + 3], 10, -1894986606), l, g, n[e + 10], 15, -1051523), m, l, n[e + 1], 21, -2054922799), v = f(v, m = f(m, l = f(l, g, v, m, n[e + 8], 6, 1873313359), g, v, n[e + 15], 10, -30611744), l, g, n[e + 6], 15, -1560198380), m, l, n[e + 13], 21, 1309151649), v = f(v, m = f(m, l = f(l, g, v, m, n[e + 4], 6, -145523070), g, v, n[e + 11], 10, -1120210379), l, g, n[e + 2], 15, 718787259), m, l, n[e + 9], 21, -343485551), l = t(l, i), g = t(g, a), v = t(v, d), m = t(m, h); return [l, g, v, m] function a(n) var t, r = "", e = 32 * n.length; for (t = 0; t < e; t += 8)r += String.fromCharCode(n[t >> 5] >>> t % 32 & 255); return r function d(n) var t, r = []; for (r[(n.length >> 2) - 1] = void 0, t = 0; t < r.length; t += 1)r[t] = 0; var e = 8 * n.length; for (t = 0; t < e; t += 8)r[t >> 5] |= (255 & n.charCodeAt(t / 8)) << t % 32; return r function h(n) return a(i(d(n), 8 * n.length)) function l(n, t) var r, e, o = d(n), u = [], c = []; for (u[15] = c[15] = void 0, o.length > 16 && (o = i(o, 8 * n.length)), r = 0; r < 16; r += 1)u[r] = 909522486 ^ o[r], c[r] = 1549556828 ^ o[r]; return e = i(u.concat(d(t)), 512 + 8 * t.length), a(i(c.concat(e), 640)) function g(n) var t, r, e = ""; for (r = 0; r < n.length; r += 1)t = n.charCodeAt(r), e += "0123456789abcdef".charAt(t >>> 4 & 15) + "0123456789abcdef".charAt(15 & t); return e function v(n) return unescape(encodeURIComponent(n)) function m(n) return h(v(n)) function p(n) return g(m(n)) function s(n, t) return l(v(n), v(t)) function C(n, t) return g(s(n, t)) function A(n, t, r) return t ? r ? s(t, n) : C(t, n) : r ? m(n) : p(n) "function" == typeof define && define.amd ? define(function () return A ) : "object" == typeof module && module.exports ? module.exports = A : n.md5 = A (this);
const GameConfig = (function ()
let _baseUrl = window.location.href.replace(/(https?.*)\\/[^\\/]*/, "$1");
let _bundleUrl = `$_baseUrl/StreamingAssets/AssetsBundles`;
let _DB_NAME = '/idbfs';
let _DB_STORE = "FILE_DATA";
let _BASE_DIR = `/idbfs/$md5(_baseUrl)`;
let _bundleDir = `$_BASE_DIR/AssetsBundles`;
return
get baseUrl() return _baseUrl; ,
get bundleUrl() return _bundleUrl; ,
get DB_NAME() return _DB_NAME; ,
get DB_STORE() return _DB_STORE; ,
get DB_BASE_DIR() return _BASE_DIR; ,
get DB_BundleDir() return _bundleDir;
;
)();
let EmUtil = (function ()
// download files from net
function _netGet(url, type, param)
// Return a new promise.
return new Promise(function (resolve, reject)
// Do the usual XHR stuff
let req = new XMLHttpRequest();
req.open('GET', url);
req.responseType = type || "arraybuffer"; // https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/responseType
req.onload = function ()
// This is called even on 404 etc
// so check the status
if (req.status == 200)
// Resolve the promise with the response text
if (param)
resolve( val: req.response, param: param );
else
resolve(req.response);
else
// Otherwise reject with the status text
// which will hopefully be a meaningful error
reject(Error(req.statusText));
;
// Handle network errors
req.onerror = function ()
reject(Error("Network Error"));
;
// Make the request
req.send();
);
// http://www.onicos.com/staff/iz/amuse/javascript/expert/utf.txt
/* utf.js - UTF-8 <=> UTF-16 convertion
*
* Copyright (C) 1999 Masanao Izumo <iz@onicos.co.jp>
* Version: 1.0
* LastModified: Dec 25 1999
* This library is free. You can redistribute it and/or modify it.
*/
function _bin2Str(array)
let out, i, len, c;
let char2, char3;
out = "";
len = array.length;
i = 0;
while (i < len)
c = array[i++];
switch (c >> 4)
case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7:
// 0xxxxxxx
out += String.fromCharCode(c);
break;
case 12: case 13:
// 110x xxxx 10xx xxxx
char2 = array[i++];
out += String.fromCharCode(((c & 0x1F) << 6) | (char2 & 0x3F));
break;
case 14:
// 1110 xxxx 10xx xxxx 10xx xxxx
char2 = array[i++];
char3 = array[i++];
out += String.fromCharCode(((c & 0x0F) << 12) |
((char2 & 0x3F) << 6) |
((char3 & 0x3F) << 0));
break;
return out;
function b64EncodeUnicode(str)
// first we use encodeURIComponent to get percent-encoded UTF-8,
// then we convert the percent encodings into raw bytes which
// can be fed into btoa.
return btoa(encodeURIComponent(str).replace(/%([0-9A-F]2)/g,
function toSolidBytes(match, p1)
return String.fromCharCode('0x' + p1);
));
function b64DecodeUnicode(str)
// Going backwards: from bytestream, to percent-encoding, to original string.
return decodeURIComponent(atob(str).split('').map(function (c)
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
).join(''));
let _setCookie = (cname, cvalue, exdays) =>
let d = new Date();
d.setTime(d.getTime() + (exdays * 24 * 60 * 60 * 1000));
cname = `$cname_$webconfig.compileType`;
cvalue = b64EncodeUnicode(cvalue);
document.cookie = `$cname=$cvalue;expires=$d.toUTCString();secure;path=/`;
let _getCookie = cname =>
cname = `$cname_$webconfig.compileType`;
let val = document.cookie.replace(new RegExp(`(?:(?:^|.*;\\\\s*)$cname\\\\s*=\\\\s*([^;]*).*$)|^.*$`, 'i'), "$1");
return b64DecodeUnicode(val);
let _delCookie = cname =>
document.cookie = `$cname=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`;
return
netGet: _netGet,
bin2Str: _bin2Str,
setCookie: _setCookie,
getCookie: _getCookie,
delCookie: _delCookie
)();
// Database Manager
// refer: https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API/Using_IndexedDB
DBManager = (function ()
const READONLY = "readonly";
const READWRITE = "readwrite";
let db = null;
let _open = () =>
if (db != null)
return Promise.resolve(db);
return new Promise((resolve, reject) =>
this.req = window.indexedDB.open(GameConfig.DB_NAME);
req.onupgradeneeded = evt =>
db = evt.currentTarget.result;
let store = db.createObjectStore(GameConfig.DB_STORE);
store.transaction.oncomplete = function (event)
let tmp = db.transaction(GameConfig.DB_STORE, READWRITE).objectStore(GameConfig.DB_STORE);
// create default folders
tmp.add( timestamp: new Date(), mode: 16877 , GameConfig.DB_BASE_DIR); // 40755
tmp.add( timestamp: new Date(), mode: 16895 , GameConfig.DB_BundleDir); // 40777
;
;
req.onsuccess = function ()
db = this.result;
resolve(db);
;
req.onerror = evt =>
reject(Error("Error openDB:" + evt.target.errorCode))
;
);
;
return
iterateAll: async callback =>
let db = await _open();
return new Promise((resolve, reject) =>
let tx = db.transaction(GameConfig.DB_STORE, READONLY);
// tx.oncomplete = resolve;
tx.onabort = reject;
tx.onerror = reject;
let store = tx.objectStore(GameConfig.DB_STORE);
let cHandler = store.openCursor();
cHandler.onsuccess = function (event)
let cursor = event.target.result;
if (cursor && callback(cursor))
cursor.continue();
else
resolve("No more entries!");
;
cHandler.onerror = evt =>
reject(Error("Cursor error!"));
;
);
,
doRead: async arr =>
let db = await _open();
return new Promise((resolve, reject) =>
let tx = db.transaction(GameConfig.DB_STORE, READONLY);
tx.oncomplete = resolve;
tx.onabort = reject;
tx.onerror = reject;
let store = tx.objectStore(GameConfig.DB_STORE);
let doJob = e =>
let req = store.get(e.path);
req.onsuccess = evt =>
e.onDBRead(evt.target.result);
;
req.onerror = evt =>
e.onDBError(evt);
;
;
if (arr.constructor === Array) // array
arr.forEach(e =>
doJob(e);
);
else if (arr.constructor === Entity) // single
doJob(arr);
else
throw "fail to read!"
);
,
doInsert: async arr =>
let db = await _open();
return new Promise((resolve, reject) =>
let tx = db.transaction(GameConfig.DB_STORE, READWRITE);
tx.oncomplete = resolve;
tx.onabort = reject;
tx.onerror = reject;
let store = tx.objectStore(GameConfig.DB_STORE);
if (arr.constructor === Array) // array
arr.forEach(e =>
store.add(e.Value, e.Path);
);
else if (arr.constructor === Entity) // single
let e = arr;
store.add(e.Value, e.Path);
else
throw "fail to read!"
);
,
doInsertOrUpdate: async arr =>
let db = await _open();
return new Promise((resolve, reject) =>
let tx = db.transaction(GameConfig.DB_STORE, READWRITE);
tx.oncomplete = resolve;
tx.onabort = reject;
tx.onerror = reject;
let store = tx.objectStore(GameConfig.DB_STORE);
if (arr.constructor === Array) // array
arr.forEach(e =>
store.put(e.Value, e.Path);
);
else if (arr.constructor === Entity) // single
let e = arr;
store.put(e.Value, e.Path);
else
throw "fail to insertOrUpdate!"
);
,
doDelete: async arr =>
let db = await _open();
return new Promise((resolve, reject) =>
let tx = db.transaction(GameConfig.DB_STORE, READWRITE);
tx.oncomplete = resolve;
tx.onabort = reject;
tx.onerror = reject;
let store = tx.objectStore(GameConfig.DB_STORE);
if (arr.constructor === Array) // array
arr.forEach(e =>
store.delete(e.Path);
);
else if (arr.constructor === Entity) // single
let e = arr;
store.delete(e.Path);
else
throw "fail to delete!"
);
,
close: () =>
if (db)
db.close();
db = null;
,
;
)();
class Entity
static genBundle(name, content)
return new Entity(name,
`$GameConfig.DB_BundleDir/$name`,
content && timestamp: new Date(), mode: 33206, contents: content ,
content ? content.byteLength : 1,
true);
constructor(name, path, value, size, isBundle)
this.name = name;
this.path = path;
this.value = value;
this.isBundle = isBundle;
this.size = size;
async checkUpdated(md5Str)
await DBManager.doRead(this);
if (md5Str != md5(this.ContentAsStr))
let tmp = await EmUtil.netGet(`$GameConfig.bundleUrl/$this.name`);
let val = new Uint8Array(tmp);
if (md5Str != md5(EmUtil.bin2Str(val)))
throw new Error(`Fail to update $entity.name`);
this.value = timestamp: new Date(), mode: 33206, contents: val ;
return false;
return true;
onDBRead(vv) this.value = vv;
onDBWrite(vv)
onDBError(vv) console.error(vv);
get Size() return this.size;
get Name() return this.name;
get Path() return this.path;
get Value() return this.value;
get Content() return this.value && this.value.contents;
get ContentAsStr() return this.value && this.value.contents && EmUtil.bin2Str(new Uint8Array(this.value.contents));
class DBInsertJob
constructor(condCount, condSize)
this.condCount = condCount || Number.MAX_SAFE_INTEGER;
this.condSize = condSize || Number.MAX_SAFE_INTEGER;
this.batches = [];
this.curBatch = [];
this.curSize = 0;
this.isWorking = false;
insert(entity)
if (entity)
this.curSize += entity.Size;
this.curBatch.push(entity);
if (this._formBatch())
this._doJob();
_formBatch(isForce)
if (this.curBatch.length > 0 &&
(isForce || this.curBatch.length >= this.condCount || this.curSize > this.condSize)) // meet the batch condition
this.batches.push(this.curBatch);
this.curBatch = [];
this.curSize = 0;
return true;
return false;
_doJob(doneEvent)
if (this.batches.length > 0 && !this.isWorking) // do job
this.isWorking = true;
(async () =>
while (this.batches.length > 0)
await DBManager.doInsertOrUpdate(this.batches.shift());
this.isWorking = false;
if (doneEvent)
doneEvent();
)();
flush()
return new Promise((resolve, reject) =>
if (this._formBatch(true))
this._doJob(resolve);
else
resolve();
);
get IsWorking()
return this.isWorking;
function EmBundleMap(_name)
let entity = Entity.genBundle(_name);
let resList = null;
return
get name()
return entity && entity.name;
,
get entity()
return entity;
,
get resList()
return resList;
,
updateMap: async (md5Str, dbJob) =>
let isUpdated = await entity.checkUpdated(md5Str);
if (!isUpdated)
dbJob.insert(entity);
resList = JSON.parse(entity.ContentAsStr);
,
updateBundles: async dict =>
try
let reg = /^AssetsBundles\\/(.*)$/;
let upds = [];
resList.forEach(r =>
let name = r.path.replace(reg, "$1");
let info = dict[name];
if (info.mStatus != 1 || info.bStatus != 1)
upds.push(info);
);
// alrady updated!!
if (upds.length == 0)
console.log(`$entity && entity.name is updated!`);
return true;
// start update
let dbJob = new DBInsertJob(20, 10 * 1024 * 1024); // 20 entities or 10M as a batch!
let waitList = [];
upds.forEach(j =>
let name = j.val.path.replace(reg, "$1");
waitList.push(new Promise((resolve, reject) =>
EmUtil.netGet(`$GameConfig.bundleUrl/$name`).then(val =>
dbJob.insert(Entity.genBundle(name, new Uint8Array(val)));
resolve();
).catch(ex =>
reject(ex);
);
));
waitList.push(new Promise((resolve, reject) =>
EmUtil.netGet(`$GameConfig.bundleUrl/$name.manifest`).then(val =>
dbJob.insert(Entity.genBundle(`$name.manifest`, new Uint8Array(val)));
resolve();
).catch(ex =>
reject(ex);
);
));
);
await Promise.all(waitList);
await dbJob.flush();
return true;
catch (ex)
console.error(ex);
return false;
,
;
;
let AllBundle = (() =>
let bundleMd5 = `$GameConfig.bundleUrl/BundleMD5.txt`;
let local = new EmBundleMap("LocalBundleMap.txt");
let patch = new EmBundleMap("BundleMap.txt");
let sprite = new EmBundleMap("SpriteSortingLayers.txt");
let fixDatabase = async () => // delete old resource, pareparing for updating
let dict = ;
let dels = new Set();
let reg = /^AssetsBundles\\/(.*)$/;
local.resList.forEach(r => dict[r.path.replace(reg, "$1")] = bStatus: 0, mStatus: 0, val: r ; );
patch.resList.forEach(r => dict[r.path.replace(reg, "$1")] = bStatus: 0, mStatus: 0, val: r ; );
let reg1 = /AssetsBundles\\/(?:([^\\/\\.]+?)\\.manifest|([^\\/\\.]+))$/;
let reg2 = /[^]+CRC: (\\d+)[^]+/i;
await DBManager.iterateAll(c =>
let m = c.key.match(reg1);
if (m)
let mName = m[1];
let bName = m[2];
if (mName) // found manifest
let info = dict[mName];
if (info)
let isValid = (c.value && c.value.contents
&& EmUtil.bin2Str(new Uint8Array(c.value.contents.slice(0, 50))).replace(reg2, "$1") == info.val.crc);
info.mStatus = isValid ? 1 : -1;
else
dels.add(`$mName.manifest`);
else if (bName) // found bundle
let info = dict[bName];
if (info)
let isValid = c.value && c.value.contents;
info.bStatus = isValid ? 1 : -1;
else
dels.add(bName);
else
throw `error when validating bundles: $c.key`;
return true;
);
Object.values(dict).filter(info => info.mStatus != 1 || info.bStatus != 1).forEach(info =>
if (info.mStatus != 0)
info.mStatus = 0;
dels.add(`$info.val.path.replace(reg, "$1").manifest`);
if (info.bStatus != 0)
info.bStatus = 0;
dels.add(info.val.path.replace(reg, "$1"));
);
if (dels.size > 0)
let tmp = [];
dels.forEach(d =>
tmp.push(new Entity(d));
);
await DBManager.doDelete(tmp);
return dict;
;
return
checkDB: async () => // validate database!
let baseFolders = [
new Entity("baseDir", GameConfig.DB_BASE_DIR),
new Entity("bundleDir", GameConfig.DB_BundleDir),
];
await DBManager.doRead(baseFolders);
if (baseFolders.filter(e => !e.Value).length > 0)
await DBManager.doInsertOrUpdate([
new Entity("baseDir", GameConfig.DB_BASE_DIR, timestamp: new Date(), mode: 16877 , 1, false),
new Entity("bundleDir", GameConfig.DB_BundleDir, timestamp: new Date(), mode: 16895 , 1, false),
]);
,
updateMap: async () => // update bundleMaps !
let res = await EmUtil.netGet(bundleMd5, "text");
let mm = res.split(",");
if (mm.length != 3)
throw new Error("The format of BundleMD5.txt is not correct!");
let dbJob = new DBInsertJob();
await Promise.all([
local.updateMap(mm[0], dbJob),
patch.updateMap(mm[1], dbJob),
sprite.updateMap(mm[2], dbJob)
]);
await dbJob.flush();
return await fixDatabase();
,
updateLocal: async dict =>
let retryTime = 0;
while (retryTime++ < 3)
let isOK = await local.updateBundles(dict);
if (isOK)
break;
else
console.warn(`failed to update $local.name at retryTime: $retryTime`);
,
updatePatch: async dict =>
let retryTime = 0;
while (retryTime++ < 3)
let isOK = await patch.updateBundles(dict);
if (isOK)
break;
else
console.warn(`failed to update $patch.name at retryTime: $retryTime`);
;
)();
let UpdateTool = (function ()
return
start: async () =>
try
await AllBundle.checkDB();
let dict = await AllBundle.updateMap();
await AllBundle.updateLocal(dict);
await AllBundle.updatePatch(dict);
catch (ex)
console.error(ex);
finally
DBManager.close();
;
)();
以上是关于一个Unity的WebGL的资源更新逻辑的主要内容,如果未能解决你的问题,请参考以下文章
更新到 Unity 2019.3 后无法编译 Unity WebGL