You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

372 lines
16 KiB

(function (global) {
'use strict';
/**
* Asset inventory from cc.assetManager only.
* @constructor
*/
function AssetsPanel() {
this.el = null;
this._cc = null;
this._rows = [];
this._bundleFilter = '';
this._typeFilter = '';
this._query = '';
this._sort = 'mem';
this._selected = null;
this._timer = 0;
}
/** @param {*} cc */
AssetsPanel.prototype.bindEngine = function (cc) {
this._cc = cc;
var self = this;
if (this._timer) clearInterval(this._timer);
this._timer = setInterval(function () {
if (!self.el || self.el.offsetParent === null) return;
self.refresh();
}, 2000);
this.refresh();
};
AssetsPanel.prototype.mount = function () {
var self = this;
var root = document.createElement('div');
root.className = 'pw-panel-inner';
root.style.height = '100%';
root.style.display = 'flex';
root.style.flexDirection = 'column';
root.innerHTML =
'<div class="pw-panel-head">' +
'<span class="pw-panel-title">资源</span>' +
'<div class="pw-panel-tools">' +
'<select id="pw-asset-bundle" class="pw-select"><option value="">分包:全部</option></select>' +
'<select id="pw-asset-type" class="pw-select">' +
'<option value="">类型:全部</option>' +
'<option value="Texture2D">纹理 Texture2D</option>' +
'<option value="SpriteFrame">图集帧 SpriteFrame</option>' +
'<option value="Prefab">预制体 Prefab</option>' +
'<option value="sp.SkeletonData">Spine</option>' +
'<option value="AudioClip">音频</option>' +
'<option value="JsonAsset">JSON</option>' +
'<option value="Other">其他</option>' +
'</select>' +
'<input id="pw-asset-q" class="pw-input" placeholder="搜索" style="width:90px" />' +
'<select id="pw-asset-sort" class="pw-select">' +
'<option value="mem">按内存↓</option>' +
'<option value="name">按名称</option>' +
'<option value="ref">按引用↓</option>' +
'<option value="type">按类型</option>' +
'</select>' +
'<button type="button" id="pw-asset-refresh" class="pw-btn">刷新</button>' +
'</div></div>' +
'<div class="pw-panel-body">' +
'<div id="pw-asset-summary" class="pw-summary"></div>' +
'<div id="pw-asset-bundles" class="pw-muted" style="margin-bottom:6px"></div>' +
'<div style="display:flex;gap:8px;min-height:0;flex:1">' +
'<div style="flex:1;overflow:auto;min-width:0"><table class="pw-table" id="pw-asset-table"><thead><tr>' +
'<th>名称</th><th>类型</th><th>分包</th><th>内存</th><th>引用</th>' +
'</tr></thead><tbody></tbody></table></div>' +
'<div style="width:180px;flex-shrink:0"><div class="pw-muted">预览</div><div class="pw-preview-box" id="pw-asset-preview"></div><div id="pw-asset-meta" class="pw-muted" style="margin-top:4px"></div></div>' +
'</div></div>';
this.el = root;
root.querySelector('#pw-asset-bundle').addEventListener('change', function (ev) {
self._bundleFilter = ev.target.value;
self.renderList();
});
root.querySelector('#pw-asset-type').addEventListener('change', function (ev) {
self._typeFilter = ev.target.value;
self.renderList();
});
root.querySelector('#pw-asset-q').addEventListener('input', function (ev) {
self._query = (ev.target.value || '').toLowerCase();
self.renderList();
});
root.querySelector('#pw-asset-sort').addEventListener('change', function (ev) {
self._sort = ev.target.value;
self.renderList();
});
root.querySelector('#pw-asset-refresh').addEventListener('click', function () { self.refresh(); });
return root;
};
AssetsPanel.prototype.refresh = function () {
var cc = this._cc;
if (!cc || !cc.assetManager) {
if (this.el) {
var sum = this.el.querySelector('#pw-asset-summary');
if (sum) sum.innerHTML = '<span class="pw-warn">引擎未就绪</span>';
}
return;
}
this._rows = this._collect(cc);
this._fillBundleSelect();
this._renderSummary(cc);
this.renderList();
};
AssetsPanel.prototype._collect = function (cc) {
var rows = [];
var seen = {};
var am = cc.assetManager;
function typeName(asset) {
if (!asset || !asset.constructor) return 'Other';
var n = asset.constructor.name || 'Other';
if (n.indexOf('SkeletonData') >= 0) return 'sp.SkeletonData';
return n;
}
function estimate(asset) {
try {
if (!asset) return null;
var n = asset.constructor && asset.constructor.name;
if (n === 'Texture2D' || (asset.width && asset.height && asset.getPixelFormat)) {
var w = asset.width || 0;
var h = asset.height || 0;
var bpp = 4;
var mips = asset.mipmapLevel > 1 ? 1.33 : 1;
return Math.round(w * h * bpp * mips);
}
if (n === 'ImageAsset' && asset.width && asset.height) {
return asset.width * asset.height * 4;
}
if (n === 'SpriteFrame' && asset.texture) {
return estimate(asset.texture);
}
if (n === 'AudioClip') {
if (asset._nativeAsset && asset._nativeAsset.byteLength) return asset._nativeAsset.byteLength;
if (typeof asset.getDuration === 'function') return Math.round(asset.getDuration() * 176400);
return null;
}
if (n === 'JsonAsset' && asset.json) {
return JSON.stringify(asset.json).length * 2;
}
if (asset.nativeUrl && asset._nativeAsset && asset._nativeAsset.byteLength) {
return asset._nativeAsset.byteLength;
}
} catch (e) {
return null;
}
return null;
}
function pushAsset(asset, bundleName) {
if (!asset || !asset.isValid) return;
var uuid = asset.uuid || asset._uuid || '';
var key = uuid || (bundleName + ':' + (asset.name || '') + ':' + typeName(asset));
if (seen[key]) return;
seen[key] = true;
var bytes = estimate(asset);
rows.push({
name: asset.name || uuid || '(asset)',
uuid: uuid,
type: typeName(asset),
bundle: bundleName || '',
ref: typeof asset.refCount === 'number' ? asset.refCount : 0,
bytes: bytes,
asset: asset,
});
}
try {
am.assets.forEach(function (asset) {
var bundleName = '';
var uuid = asset && (asset.uuid || asset._uuid);
try {
if (uuid && am.bundles) {
am.bundles.forEach(function (bundle, name) {
if (bundleName) return;
if (bundle.getAssetInfo && bundle.getAssetInfo(uuid)) {
bundleName = name || bundle.name || '';
}
});
}
} catch (e2) { /* ignore */ }
pushAsset(asset, bundleName);
});
} catch (e3) {
console.warn('[Workbench] assets enumerate', e3);
}
return rows;
};
AssetsPanel.prototype._fillBundleSelect = function () {
var sel = this.el.querySelector('#pw-asset-bundle');
if (!sel) return;
var cur = this._bundleFilter;
var names = {};
for (var i = 0; i < this._rows.length; i++) {
if (this._rows[i].bundle) names[this._rows[i].bundle] = true;
}
var html = '<option value="">分包:全部</option>';
var keys = Object.keys(names).sort();
for (var k = 0; k < keys.length; k++) {
html += '<option value="' + escapeAttr(keys[k]) + '"' + (keys[k] === cur ? ' selected' : '') + '>' + escapeHtml(keys[k]) + '</option>';
}
sel.innerHTML = html;
};
AssetsPanel.prototype._renderSummary = function (cc) {
var sum = this.el.querySelector('#pw-asset-summary');
var bun = this.el.querySelector('#pw-asset-bundles');
var total = 0;
var est = 0;
var na = 0;
var byBundle = {};
for (var i = 0; i < this._rows.length; i++) {
var r = this._rows[i];
total++;
if (r.bytes == null) na++;
else est += r.bytes;
var b = r.bundle || '(none)';
if (!byBundle[b]) byBundle[b] = { count: 0, bytes: 0, na: 0 };
byBundle[b].count++;
if (r.bytes == null) byBundle[b].na++;
else byBundle[b].bytes += r.bytes;
}
var tex = 0;
var buf = 0;
if (cc.profiler && cc.profiler.stats) {
if (cc.profiler.stats.textureMemory) tex = cc.profiler.stats.textureMemory.counter.value;
if (cc.profiler.stats.bufferMemory) buf = cc.profiler.stats.bufferMemory.counter.value;
}
var bundleCount = 0;
try { cc.assetManager.bundles.forEach(function () { bundleCount++; }); } catch (e) { /* */ }
if (sum) {
sum.innerHTML =
'<span>分包 <b>' + bundleCount + '</b></span>' +
'<span>资源 <b>' + total + '</b></span>' +
'<span>估算 <b>' + formatBytes(est) + '</b>' + (na ? ' <span class="pw-warn">(' + na + ' 无法估算)</span>' : '') + '</span>' +
'<span>纹理显存 <b>' + (Math.round(tex * 100) / 100) + 'MB</b></span>' +
'<span>缓冲显存 <b>' + (Math.round(buf * 100) / 100) + 'MB</b></span>';
}
if (bun) {
var parts = [];
var bkeys = Object.keys(byBundle).sort();
for (var j = 0; j < bkeys.length; j++) {
var info = byBundle[bkeys[j]];
parts.push(bkeys[j] + ': ' + info.count + ' / ' + formatBytes(info.bytes) + (info.na ? ' (' + info.na + ' n/a)' : ''));
}
bun.textContent = parts.join(' · ');
}
};
AssetsPanel.prototype.renderList = function () {
var tbody = this.el.querySelector('#pw-asset-table tbody');
if (!tbody) return;
var rows = this._rows.slice();
var self = this;
rows = rows.filter(function (r) {
if (self._bundleFilter && r.bundle !== self._bundleFilter) return false;
if (self._typeFilter) {
if (self._typeFilter === 'Other') {
var known = { Texture2D: 1, SpriteFrame: 1, Prefab: 1, 'sp.SkeletonData': 1, AudioClip: 1, JsonAsset: 1 };
if (known[r.type]) return false;
} else if (r.type !== self._typeFilter && r.type.indexOf(self._typeFilter) < 0) {
return false;
}
}
if (self._query && (r.name + ' ' + r.uuid + ' ' + r.type).toLowerCase().indexOf(self._query) < 0) return false;
return true;
});
rows.sort(function (a, b) {
if (self._sort === 'name') return a.name.localeCompare(b.name);
if (self._sort === 'ref') return b.ref - a.ref;
if (self._sort === 'type') return a.type.localeCompare(b.type);
var ab = a.bytes == null ? -1 : a.bytes;
var bb = b.bytes == null ? -1 : b.bytes;
return bb - ab;
});
var html = [];
var limit = Math.min(rows.length, 400);
for (var i = 0; i < limit; i++) {
var r = rows[i];
var sel = self._selected && self._selected.uuid === r.uuid ? ' selected' : '';
html.push(
'<tr class="' + sel + '" data-idx="' + i + '">' +
'<td title="' + escapeAttr(r.uuid) + '">' + escapeHtml(r.name) + '</td>' +
'<td>' + escapeHtml(r.type) + '</td>' +
'<td>' + escapeHtml(r.bundle || '-') + '</td>' +
'<td class="pw-num">' + (r.bytes == null ? '<span class="pw-warn">无法估算</span>' : formatBytes(r.bytes)) + '</td>' +
'<td class="pw-num">' + r.ref + '</td></tr>'
);
}
if (rows.length > limit) html.push('<tr><td colspan="5" class="pw-muted">仅显示前 ' + limit + ' / ' + rows.length + '</td></tr>');
tbody.innerHTML = html.join('') || '<tr><td colspan="5" class="pw-muted">暂无资源</td></tr>';
this._filtered = rows;
var trs = tbody.querySelectorAll('tr[data-idx]');
for (var t = 0; t < trs.length; t++) {
trs[t].addEventListener('click', function (ev) {
var idx = parseInt(ev.currentTarget.getAttribute('data-idx'), 10);
self._selected = self._filtered[idx];
self.renderList();
self._preview(self._selected);
});
}
};
AssetsPanel.prototype._preview = function (row) {
var box = this.el.querySelector('#pw-asset-preview');
var meta = this.el.querySelector('#pw-asset-meta');
if (!box || !meta) return;
box.innerHTML = '';
if (!row || !row.asset) {
meta.textContent = '';
return;
}
meta.textContent = row.type + ' | ' + (row.bytes == null ? '无法估算' : formatBytes(row.bytes)) + ' | ' + (row.uuid || '');
var asset = row.asset;
var tex = null;
if (row.type === 'Texture2D') tex = asset;
else if (row.type === 'SpriteFrame' && asset.texture) tex = asset.texture;
if (tex && tex.width && tex.height) {
var url = tex.nativeUrl || (asset && asset.nativeUrl) || '';
if (url) {
var img = document.createElement('img');
img.alt = row.name;
img.style.maxWidth = '100%';
img.style.maxHeight = '160px';
img.src = url;
img.onerror = function () {
box.innerHTML = '<span class="pw-muted">' + tex.width + 'x' + tex.height + '</span>';
};
box.appendChild(img);
} else {
box.innerHTML = '<span class="pw-muted">' + tex.width + 'x' + tex.height + '(无预览地址)</span>';
}
meta.textContent += ' | ' + tex.width + 'x' + tex.height + '(估算)';
return;
}
if (row.type === 'JsonAsset' && asset.json) {
var pre = document.createElement('pre');
pre.style.cssText = 'max-height:160px;overflow:auto;margin:0;font-size:10px;text-align:left;width:100%';
var text = JSON.stringify(asset.json, null, 0);
pre.textContent = text.length > 800 ? text.slice(0, 800) + '…' : text;
box.appendChild(pre);
return;
}
if (row.type === 'AudioClip') {
box.innerHTML = '<span class="pw-muted">Audio ' + (typeof asset.getDuration === 'function' ? asset.getDuration().toFixed(2) + 's' : '') + '</span>';
return;
}
box.innerHTML = '<span class="pw-muted">无法预览</span>';
};
function formatBytes(n) {
if (n == null || isNaN(n)) return '无法估算';
if (n < 1024) return n + 'B';
if (n < 1024 * 1024) return (n / 1024).toFixed(1) + 'KB';
return (n / (1024 * 1024)).toFixed(2) + 'MB';
}
function escapeHtml(s) {
return String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
}
function escapeAttr(s) {
return escapeHtml(s).replace(/"/g, '&quot;');
}
global.PWAssetsPanel = AssetsPanel;
})(window);