(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 = '
' + '资源' + '
' + '' + '' + '' + '' + '' + '
' + '
' + '
' + '
' + '
' + '
' + '' + '
名称类型分包内存引用
' + '
预览
' + '
'; 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 = '引擎未就绪'; } 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 = ''; var keys = Object.keys(names).sort(); for (var k = 0; k < keys.length; k++) { html += ''; } 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 = '分包 ' + bundleCount + '' + '资源 ' + total + '' + '估算 ' + formatBytes(est) + '' + (na ? ' (' + na + ' 无法估算)' : '') + '' + '纹理显存 ' + (Math.round(tex * 100) / 100) + 'MB' + '缓冲显存 ' + (Math.round(buf * 100) / 100) + 'MB'; } 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( '' + '' + escapeHtml(r.name) + '' + '' + escapeHtml(r.type) + '' + '' + escapeHtml(r.bundle || '-') + '' + '' + (r.bytes == null ? '无法估算' : formatBytes(r.bytes)) + '' + '' + r.ref + '' ); } if (rows.length > limit) html.push('仅显示前 ' + limit + ' / ' + rows.length + ''); tbody.innerHTML = html.join('') || '暂无资源'; 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 = '' + tex.width + 'x' + tex.height + ''; }; box.appendChild(img); } else { box.innerHTML = '' + tex.width + 'x' + tex.height + '(无预览地址)'; } 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 = 'Audio ' + (typeof asset.getDuration === 'function' ? asset.getDuration().toFixed(2) + 's' : '') + ''; return; } box.innerHTML = '无法预览'; }; 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, '&').replace(//g, '>'); } function escapeAttr(s) { return escapeHtml(s).replace(/"/g, '"'); } global.PWAssetsPanel = AssetsPanel; })(window);