(function (global) { 'use strict'; var CHANNELS = ['perf', 'hierarchy', 'inspector', 'assets', 'console', 'markers', 'pick']; var PERF_DEFAULT_MS = 500; var HIER_DEFAULT_MS = 1500; var ASSETS_DEFAULT_MS = 2500; var PICK_RECT_MS = 100; var CONSOLE_FLUSH_MS = 100; /** * 游戏页探针:默认休眠,按订阅激活;经 postMessage 与壳通信。 * @constructor */ function WorkbenchAgent() { this.cc = null; this._subs = {}; this._hibernating = false; this._parentOrigin = '*'; this._reqId = 0; this._pending = {}; this._timers = {}; this._selectedUuid = ''; this._pickMode = 'game'; this._overlay = null; this._consoleOrig = null; this._consoleBuf = []; this._consoleFlushTimer = 0; this._consoleObjs = {}; this._consoleObjSeq = 1; this._hierSnap = null; this._boundMessage = this._onMessage.bind(this); this._sampler = null; this._markers = null; this._device = null; this._paused = false; this._fpsOn = false; for (var i = 0; i < CHANNELS.length; i++) this._subs[CHANNELS[i]] = 0; } WorkbenchAgent.prototype.boot = function () { var self = this; if (global.PWDevicePreview && typeof global.PWDevicePreview.hideOfficialToolbar === 'function') { global.PWDevicePreview.hideOfficialToolbar(); } window.addEventListener('message', this._boundMessage); this._ensureOverlay(); this._waitForEngine(function (cc) { self.cc = cc; self._sampler = new global.PWPerfSampler(); self._sampler.bindEngine(cc); self._sampler.intervalMs = PERF_DEFAULT_MS; self._sampler.onSample(function () { if (!self._isActive('perf')) return; self._post({ type: 'event', name: 'perf.sample', data: self._perfPayload() }); }); self._markers = new global.PWAutoMarkers(self._sampler); self._device = new global.PWDevicePreview(); self._device.restore(); var center = document.getElementById('pw-center'); var content = document.getElementById('content'); if (center && content) self._device.mount(center, content); self._device.apply(false); self._post({ type: 'event', name: 'agent.ready', data: { ok: true } }); setTimeout(function () { self._device.apply(false); }, 100); }); }; WorkbenchAgent.prototype._waitForEngine = function (done) { var self = this; var tries = 0; var timer = setInterval(function () { tries++; var cc = global.cc && global.cc.director ? global.cc : (global.CocosEngine && global.CocosEngine.director ? global.CocosEngine : null); if (cc && cc.director && cc.game) { var scene = null; try { scene = cc.director.getScene(); } catch (e) { scene = null; } if (scene || tries > 40) { clearInterval(timer); done(cc); return; } } if (tries >= 300) { clearInterval(timer); self._post({ type: 'event', name: 'agent.ready', data: { ok: false } }); } }, 100); }; WorkbenchAgent.prototype._post = function (msg) { msg.source = 'pw-agent'; try { if (window.parent && window.parent !== window) { window.parent.postMessage(msg, this._parentOrigin); } } catch (e) { /* */ } }; WorkbenchAgent.prototype._onMessage = function (ev) { var msg = ev.data; if (!msg || msg.source !== 'pw-bridge') return; if (ev.origin && ev.origin !== 'null') this._parentOrigin = ev.origin; var self = this; var reply = function (ok, result, error) { if (msg.id == null) return; self._post({ type: 'rpc-result', id: msg.id, ok: ok, result: result, error: error || '' }); }; try { var result = this._dispatch(msg.method, msg.params || {}); if (result && typeof result.then === 'function') { result.then(function (r) { reply(true, r); }, function (err) { reply(false, null, String(err && err.message ? err.message : err)); }); } else { reply(true, result); } } catch (e) { reply(false, null, String(e && e.message ? e.message : e)); } }; WorkbenchAgent.prototype._dispatch = function (method, params) { switch (method) { case 'ping': return { pong: true, hibernating: this._hibernating }; case 'hibernate': return this.hibernate(); case 'wake': return this.wake(); case 'subscribe': return this.subscribe(params.channel); case 'unsubscribe': return this.unsubscribe(params.channel); case 'perf.get': return this._perfPayload(); case 'perf.clear': if (this._sampler) this._sampler.clear(); return true; case 'perf.mark': if (this._sampler) this._sampler.userMark(params.text || ''); return true; case 'perf.setInterval': if (this._sampler) this._sampler.setIntervalMs(params.ms || PERF_DEFAULT_MS); return true; case 'perf.setCapacity': if (this._sampler) this._sampler.setCapacity(params.n || 300); return true; case 'perf.setPaused': if (this._sampler) this._sampler.paused = !!params.paused; return true; case 'hier.getTree': return this._getHierTree(); case 'hier.select': return this._selectNode(params.uuid); case 'node.get': return this._getNodeProps(params.uuid); case 'node.set': return this._setNodeProp(params.uuid, params.fid, params.value); case 'assets.list': return this._listAssets(); case 'pick.setMode': return this._setPickMode(params.mode); case 'pick.select': return this._selectNode(params.uuid); case 'game.pause': return this._setPaused(true); case 'game.resume': return this._setPaused(false); case 'game.step': return this._stepFrame(); case 'game.fps': return this._setFps(!!params.on); case 'device.list': return this._device ? this._device.list() : []; case 'device.set': if (this._device) this._device.setDevice(params.id); return true; case 'device.rotate': return this._device ? this._device.toggleRotate() : false; case 'device.apply': if (this._device) this._device.apply(!!params.force); return true; case 'device.getState': return this._device ? { id: this._device.deviceId, rotated: this._device.rotated, } : null; case 'console.inspect': return this._inspectConsoleObj(params.oid, params.path || ''); default: throw new Error('unknown method: ' + method); } }; WorkbenchAgent.prototype._isActive = function (ch) { return !this._hibernating && this._subs[ch] > 0; }; WorkbenchAgent.prototype.subscribe = function (channel) { if (!this._subs.hasOwnProperty(channel)) return { active: false }; this._subs[channel] += 1; if (this._subs[channel] === 1) this._onChannelOn(channel); return { active: this._isActive(channel), count: this._subs[channel] }; }; WorkbenchAgent.prototype.unsubscribe = function (channel) { if (!this._subs.hasOwnProperty(channel)) return { active: false }; this._subs[channel] = Math.max(0, this._subs[channel] - 1); if (this._subs[channel] === 0) this._onChannelOff(channel); return { active: this._isActive(channel), count: this._subs[channel] }; }; WorkbenchAgent.prototype.hibernate = function () { this._hibernating = true; for (var i = 0; i < CHANNELS.length; i++) this._onChannelOff(CHANNELS[i]); this._post({ type: 'event', name: 'agent.hibernate', data: { on: true } }); return true; }; WorkbenchAgent.prototype.wake = function () { this._hibernating = false; for (var i = 0; i < CHANNELS.length; i++) { if (this._subs[CHANNELS[i]] > 0) this._onChannelOn(CHANNELS[i]); } this._post({ type: 'event', name: 'agent.hibernate', data: { on: false } }); return true; }; WorkbenchAgent.prototype._onChannelOn = function (ch) { if (this._hibernating) return; var self = this; if (ch === 'perf') { if (this._sampler) this._sampler.start(); } else if (ch === 'markers') { if (this._markers && this.cc && !this._markers._pollTimer) this._markers.bindEngine(this.cc); } else if (ch === 'hierarchy') { this._hierFp = ''; this._clearTimer('hier'); this._timers.hier = setInterval(function () { if (!self._isActive('hierarchy')) return; self._pushHierIfChanged(); }, HIER_DEFAULT_MS); this._pushHierIfChanged(true); } else if (ch === 'assets') { this._clearTimer('assets'); this._timers.assets = setInterval(function () { if (!self._isActive('assets')) return; self._listAssetsAsync(function (data) { self._post({ type: 'event', name: 'assets.update', data: data }); }); }, ASSETS_DEFAULT_MS); } else if (ch === 'console') { this._hookConsole(true); } else if (ch === 'pick') { this._bindCanvasPick(true); this._clearTimer('pickRect'); this._timers.pickRect = setInterval(function () { if (!self._isActive('pick') || !self._selectedUuid) return; self._updateOverlay(); var rect = self._getOverlayRect(); if (rect) self._post({ type: 'event', name: 'pick.rect', data: rect }); }, PICK_RECT_MS); } }; WorkbenchAgent.prototype._onChannelOff = function (ch) { if (ch === 'perf') { if (this._sampler) this._sampler.stop(); } else if (ch === 'markers') { if (this._markers) this._markers.dispose(); } else if (ch === 'hierarchy') { this._clearTimer('hier'); } else if (ch === 'assets') { this._clearTimer('assets'); } else if (ch === 'console') { this._hookConsole(false); } else if (ch === 'pick') { this._bindCanvasPick(false); this._clearTimer('pickRect'); if (this._overlay) this._overlay.hidden = true; } }; WorkbenchAgent.prototype._clearTimer = function (key) { if (this._timers[key]) { clearInterval(this._timers[key]); this._timers[key] = 0; } }; WorkbenchAgent.prototype._perfPayload = function () { if (!this._sampler) return null; return { times: this._sampler.times.slice(), series: copySeries(this._sampler.series), markers: this._sampler.markers.slice(), latest: this._sampler.latest(), }; }; WorkbenchAgent.prototype._getHierTree = function () { var cc = this.cc; if (!cc || !cc.director) return null; var scene = cc.director.getScene(); if (!scene) return null; var selected = this._selectedUuid; return { selected: selected, root: serializeNode(scene, 0, 48), }; }; /** 层级指纹变化才推送,避免无改动全量刷壳 */ WorkbenchAgent.prototype._pushHierIfChanged = function (force) { var tree = this._getHierTree(); var fp = hierFingerprint(tree && tree.root) + '|' + (tree && tree.selected || ''); if (!force && fp === this._hierFp) return; this._hierFp = fp; this._post({ type: 'event', name: 'hier.diff', data: tree }); }; /** 空闲切片枚举资源,降低峰值卡顿 */ WorkbenchAgent.prototype._listAssetsAsync = function (done) { var cc = this.cc; if (!cc || !cc.assetManager) { done({ rows: [], bundles: [] }); return; } var bundleNames = []; var entries = []; try { if (cc.assetManager.bundles) { cc.assetManager.bundles.forEach(function (bundle, name) { bundleNames.push(name || (bundle && bundle.name) || ''); }); } if (cc.assetManager.assets) { cc.assetManager.assets.forEach(function (asset, uuid) { entries.push({ asset: asset, uuid: uuid }); }); } } catch (e) { done({ rows: [], bundles: bundleNames }); return; } var rows = []; var idle = global.PWIdle; var visit = function (entry) { var asset = entry.asset; if (!asset) return; var type = (asset.constructor && asset.constructor.name) || 'Other'; var mem = 0; if (asset.width && asset.height) mem = (asset.width * asset.height * 4) / (1024 * 1024); rows.push({ uuid: entry.uuid, name: asset.name || entry.uuid, type: type, bundle: '', mem: mem, ref: asset.refCount != null ? asset.refCount : 0, }); }; if (idle && idle.forEachChunk) { idle.forEachChunk(entries, visit, function () { done({ rows: rows, bundles: bundleNames }); }, 2); } else { for (var i = 0; i < entries.length; i++) visit(entries[i]); done({ rows: rows, bundles: bundleNames }); } }; WorkbenchAgent.prototype._findNode = function (uuid) { var cc = this.cc; if (!cc || !cc.director || !uuid) return null; var scene = cc.director.getScene(); if (!scene) return null; var found = null; walk(scene, function (n) { var id = nodeId(n); if (id === uuid) found = n; }); return found; }; WorkbenchAgent.prototype._selectNode = function (uuid) { this._selectedUuid = uuid || ''; var node = this._findNode(uuid); this._updateOverlay(); this._post({ type: 'event', name: 'pick.selected', data: { uuid: this._selectedUuid } }); return { uuid: this._selectedUuid, ok: !!node }; }; WorkbenchAgent.prototype._getNodeProps = function (uuid) { var node = this._findNode(uuid); if (!node) return null; var cc = this.cc; var p = node.getPosition ? node.getPosition() : node.position; var e = node.eulerAngles || { x: 0, y: 0, z: 0 }; var s = node.getScale ? node.getScale() : node.scale; var uit = node.getComponent && cc ? node.getComponent(cc.UITransform) : null; var comps = []; if (node.components) { for (var i = 0; i < node.components.length; i++) { var comp = node.components[i]; if (!comp || !comp.constructor) continue; var cname = comp.constructor.name; if (cname === 'UITransform') continue; var fields = []; var keys = Object.keys(comp); var shown = 0; for (var k = 0; k < keys.length && shown < 24; k++) { var key = keys[k]; if (!key || key.charAt(0) === '_' || key === 'node' || key === 'uuid') continue; var val = comp[key]; var t = typeof val; if (t === 'number' || t === 'boolean' || t === 'string') { fields.push({ key: key, type: t, value: val }); shown++; } } comps.push({ name: cname, index: i, fields: fields }); } } return { uuid: nodeId(node), name: node.name || '', active: !!node.active, layer: node.layer, pos: { x: p.x, y: p.y, z: p.z }, euler: { x: e.x, y: e.y, z: e.z }, scale: { x: s.x, y: s.y, z: s.z }, ui: uit ? { width: uit.width, height: uit.height, anchorX: uit.anchorX, anchorY: uit.anchorY, } : null, comps: comps, }; }; WorkbenchAgent.prototype._setNodeProp = function (uuid, fid, value) { var node = this._findNode(uuid); if (!node || !fid) return false; var cc = this.cc; var uit = node.getComponent && cc ? node.getComponent(cc.UITransform) : null; try { if (fid === 'name') node.name = String(value); else if (fid === 'active') node.active = !!value; else if (fid === 'layer') node.layer = Number(value) || 0; else if (fid === 'px' || fid === 'py' || fid === 'pz') { var p = node.getPosition ? node.getPosition() : node.position; var x = fid === 'px' ? Number(value) : p.x; var y = fid === 'py' ? Number(value) : p.y; var z = fid === 'pz' ? Number(value) : p.z; if (typeof node.setPosition === 'function') node.setPosition(x, y, z); else if (cc && cc.Vec3) node.position = new cc.Vec3(x, y, z); } else if (fid === 'rx' || fid === 'ry' || fid === 'rz') { var e = node.eulerAngles || { x: 0, y: 0, z: 0 }; var ex = fid === 'rx' ? Number(value) : e.x; var ey = fid === 'ry' ? Number(value) : e.y; var ez = fid === 'rz' ? Number(value) : e.z; if (typeof node.setRotationFromEuler === 'function') node.setRotationFromEuler(ex, ey, ez); else if (cc && cc.Vec3) node.eulerAngles = new cc.Vec3(ex, ey, ez); } else if (fid === 'sx' || fid === 'sy' || fid === 'sz') { var sc = node.getScale ? node.getScale() : node.scale; var sx = fid === 'sx' ? Number(value) : sc.x; var sy = fid === 'sy' ? Number(value) : sc.y; var sz = fid === 'sz' ? Number(value) : sc.z; if (typeof node.setScale === 'function') node.setScale(sx, sy, sz); else if (cc && cc.Vec3) node.scale = new cc.Vec3(sx, sy, sz); } else if (uit && (fid === 'uiw' || fid === 'uih' || fid === 'uax' || fid === 'uay')) { if (fid === 'uiw') uit.width = Number(value); if (fid === 'uih') uit.height = Number(value); if (fid === 'uax') uit.anchorX = Number(value); if (fid === 'uay') uit.anchorY = Number(value); } else if (fid.indexOf('c') === 0 && fid.indexOf('_') > 0) { var parts = fid.split('_'); var ci = parseInt(parts[0].slice(1), 10); var key = parts.slice(1).join('_'); if (node.components && node.components[ci]) node.components[ci][key] = value; } return true; } catch (e) { console.warn('[Agent] set prop', fid, e); return false; } }; WorkbenchAgent.prototype._listAssets = function () { var cc = this.cc; if (!cc || !cc.assetManager) return { rows: [], bundles: [] }; var rows = []; var bundleNames = []; try { if (cc.assetManager.bundles) { cc.assetManager.bundles.forEach(function (bundle, name) { bundleNames.push(name || (bundle && bundle.name) || ''); }); } if (cc.assetManager.assets) { cc.assetManager.assets.forEach(function (asset, uuid) { if (!asset) return; var type = (asset.constructor && asset.constructor.name) || 'Other'; var mem = 0; if (asset.width && asset.height) mem = (asset.width * asset.height * 4) / (1024 * 1024); rows.push({ uuid: uuid, name: asset.name || uuid, type: type, bundle: '', mem: mem, ref: asset.refCount != null ? asset.refCount : 0, }); }); } } catch (e) { console.warn('[Agent] assets', e); } return { rows: rows, bundles: bundleNames }; }; WorkbenchAgent.prototype._setPickMode = function (mode) { this._pickMode = mode === 'inspect' ? 'inspect' : 'game'; document.documentElement.classList.toggle('pw-inspect', this._pickMode === 'inspect'); this._bindCanvasPick(this._isActive('pick')); return this._pickMode; }; WorkbenchAgent.prototype._bindCanvasPick = function (on) { var canvas = document.getElementById('GameCanvas'); if (!canvas) return; canvas.removeEventListener('pointerdown', this._onPickDown, true); if (on && this._pickMode === 'inspect') { if (!this._onPickDown) { var self = this; this._onPickDown = function (ev) { if (self._pickMode !== 'inspect') return; ev.preventDefault(); ev.stopPropagation(); var uuid = self._hitTest(ev.clientX, ev.clientY); self._selectNode(uuid); if (self._isActive('inspector') || self._isActive('hierarchy')) { self._post({ type: 'event', name: 'hier.diff', data: self._getHierTree() }); } }; } canvas.addEventListener('pointerdown', this._onPickDown, true); } }; WorkbenchAgent.prototype._hitTest = function (clientX, clientY) { var cc = this.cc; var canvas = document.getElementById('GameCanvas'); if (!cc || !canvas) return ''; var scene = cc.director.getScene(); if (!scene) return ''; var stack = []; walk(scene, function (n) { stack.push(n); }); for (var i = stack.length - 1; i >= 0; i--) { var node = stack[i]; if (!node || !node.activeInHierarchy) continue; var uit = node.getComponent && node.getComponent(cc.UITransform); if (!uit) continue; var box = this._nodeClientRect(node); if (!box) continue; if (clientX >= box.left && clientX <= box.right && clientY >= box.top && clientY <= box.bottom) { return nodeId(node); } } return ''; }; WorkbenchAgent.prototype._ensureOverlay = function () { var el = document.getElementById('pw-pick-overlay'); if (!el) { el = document.createElement('div'); el.id = 'pw-pick-overlay'; el.className = 'pw-pick-overlay'; el.hidden = true; document.body.appendChild(el); } this._overlay = el; }; WorkbenchAgent.prototype._nodeClientRect = function (node) { var cc = this.cc; var canvas = document.getElementById('GameCanvas'); if (!cc || !canvas || !node || !node.isValid) return null; var uit = node.getComponent && node.getComponent(cc.UITransform); if (!uit || typeof uit.getBoundingBoxToWorld !== 'function') return null; var box = uit.getBoundingBoxToWorld(); if (!box) return null; var canvasRect = canvas.getBoundingClientRect(); var vs = cc.view && cc.view.getVisibleSize ? cc.view.getVisibleSize() : { width: canvasRect.width, height: canvasRect.height }; var vw = Math.max(1, vs.width); var vh = Math.max(1, vs.height); var corners = [ { x: box.x, y: box.y }, { x: box.x + box.width, y: box.y }, { x: box.x + box.width, y: box.y + box.height }, { x: box.x, y: box.y + box.height }, ]; var xs = []; var ys = []; for (var i = 0; i < 4; i++) { xs.push(canvasRect.left + (corners[i].x / vw + 0.5) * canvasRect.width); ys.push(canvasRect.top + (0.5 - corners[i].y / vh) * canvasRect.height); } var left = Math.min.apply(null, xs); var right = Math.max.apply(null, xs); var top = Math.min.apply(null, ys); var bottom = Math.max.apply(null, ys); return { left: left, top: top, right: right, bottom: bottom, width: Math.max(2, right - left), height: Math.max(2, bottom - top) }; }; WorkbenchAgent.prototype._updateOverlay = function () { var node = this._findNode(this._selectedUuid); var overlay = this._overlay; if (!overlay) return; if (!node) { overlay.hidden = true; return; } var box = this._nodeClientRect(node); if (!box) { overlay.hidden = true; return; } overlay.hidden = false; overlay.style.position = 'fixed'; overlay.style.left = box.left + 'px'; overlay.style.top = box.top + 'px'; overlay.style.width = box.width + 'px'; overlay.style.height = box.height + 'px'; }; WorkbenchAgent.prototype._getOverlayRect = function () { var overlay = this._overlay; if (!overlay || overlay.hidden) return null; return { left: parseFloat(overlay.style.left) || 0, top: parseFloat(overlay.style.top) || 0, width: parseFloat(overlay.style.width) || 0, height: parseFloat(overlay.style.height) || 0, }; }; WorkbenchAgent.prototype._hookConsole = function (on) { var self = this; if (on) { if (this._consoleOrig) return; this._consoleOrig = { log: console.log.bind(console), info: console.info.bind(console), warn: console.warn.bind(console), error: console.error.bind(console), debug: console.debug.bind(console), }; var wrap = function (level, fn) { return function () { var args = Array.prototype.slice.call(arguments); self._pushConsole(level, args); return fn.apply(console, args); }; }; console.log = wrap('log', this._consoleOrig.log); console.info = wrap('info', this._consoleOrig.info); console.warn = wrap('warn', this._consoleOrig.warn); console.error = wrap('error', this._consoleOrig.error); console.debug = wrap('debug', this._consoleOrig.debug); } else if (this._consoleOrig) { console.log = this._consoleOrig.log; console.info = this._consoleOrig.info; console.warn = this._consoleOrig.warn; console.error = this._consoleOrig.error; console.debug = this._consoleOrig.debug; this._consoleOrig = null; if (this._consoleFlushTimer) { clearTimeout(this._consoleFlushTimer); this._consoleFlushTimer = 0; } this._flushConsole(); } }; WorkbenchAgent.prototype._pushConsole = function (level, args) { var entry = this._serializeConsoleArgs(level, args); this._consoleBuf.push(entry); if (this._consoleBuf.length > 200) this._consoleBuf.splice(0, this._consoleBuf.length - 200); var self = this; if (!this._consoleFlushTimer) { this._consoleFlushTimer = setTimeout(function () { self._consoleFlushTimer = 0; self._flushConsole(); }, CONSOLE_FLUSH_MS); } }; WorkbenchAgent.prototype._flushConsole = function () { if (!this._consoleBuf.length) return; var batch = this._consoleBuf.splice(0, this._consoleBuf.length); this._post({ type: 'event', name: 'console.batch', data: { items: batch } }); }; /** * 将 console 参数序列化为可 postMessage 的富结构(保留 %c/%s 等,对象可展开)。 * 原始 console.* 仍会调用,浏览器 DevTools 打印不受影响。 */ WorkbenchAgent.prototype._serializeConsoleArgs = function (level, args) { var parts = []; var plain = ''; var FMT_RE = /%(?:%|[csdifoO])/; if (args.length && typeof args[0] === 'string' && FMT_RE.test(args[0])) { var formatted = this._formatConsole(args); parts = formatted.parts; plain = formatted.plain; for (var r = 0; r < formatted.rest.length; r++) { var rp = this._valuePart(formatted.rest[r]); parts.push(rp); plain += (plain ? ' ' : '') + (rp.plain || ''); } } else { for (var i = 0; i < args.length; i++) { var p = this._valuePart(args[i], i === 0 && typeof args[i] === 'string'); parts.push(p); plain += (i ? ' ' : '') + (p.plain || ''); } } return { id: 'c' + Date.now() + '_' + Math.random().toString(36).slice(2, 7), level: level, t: Date.now(), parts: parts, plain: plain, }; }; WorkbenchAgent.prototype._formatConsole = function (args) { var fmt = args[0]; var ai = 1; var parts = []; var plain = ''; var style = ''; var i = 0; while (i < fmt.length) { if (fmt.charAt(i) === '%' && i + 1 < fmt.length) { var spec = fmt.charAt(i + 1); if (spec === '%') { parts.push({ kind: 'styled', text: '%', css: style }); plain += '%'; i += 2; continue; } if (spec === 'c') { style = typeof args[ai] === 'string' ? args[ai] : ''; ai++; i += 2; continue; } var val = ai < args.length ? args[ai] : undefined; ai++; var text = ''; if (spec === 's') text = val == null ? 'null' : String(val); else if (spec === 'd' || spec === 'i') text = String(parseInt(val, 10) || 0); else if (spec === 'f') text = String(Number(val)); else if (spec === 'o' || spec === 'O') { var vp = this._valuePart(val); parts.push(vp); plain += vp.plain || ''; i += 2; continue; } else { text = '%' + spec; } parts.push({ kind: 'styled', text: text, css: style }); plain += text; i += 2; continue; } var start = i; while (i < fmt.length && fmt.charAt(i) !== '%') i++; var lit = fmt.slice(start, i); if (lit) { parts.push({ kind: 'styled', text: lit, css: style }); plain += lit; } } return { parts: parts, plain: plain, rest: args.slice(ai) }; }; WorkbenchAgent.prototype._valuePart = function (value, bareString) { if (value === null) return { kind: 'null', plain: 'null' }; var t = typeof value; if (t === 'undefined') return { kind: 'undefined', plain: 'undefined' }; if (t === 'string') { return bareString ? { kind: 'text', text: value, plain: value } : { kind: 'string', text: value, plain: '"' + value + '"' }; } if (t === 'number' || t === 'boolean' || t === 'bigint') { return { kind: t, value: t === 'bigint' ? String(value) : value, plain: String(value) }; } if (t === 'symbol') return { kind: 'symbol', text: String(value), plain: String(value) }; if (t === 'function') { var fname = value.name || 'anonymous'; return { kind: 'function', name: fname, plain: 'ƒ ' + fname + '()' }; } if (value instanceof Error) { return { kind: 'error', name: value.name || 'Error', message: value.message || '', stack: value.stack || '', plain: (value.name || 'Error') + ': ' + (value.message || ''), }; } var oid = 'o' + (this._consoleObjSeq++); this._consoleObjs[oid] = value; var isArray = Array.isArray(value); var ctor = (value && value.constructor && value.constructor.name) || (isArray ? 'Array' : 'Object'); var preview = isArray ? ('Array(' + value.length + ')') : ctor; try { if (isArray) { var bits = []; for (var i = 0; i < Math.min(value.length, 5); i++) bits.push(briefVal(value[i])); if (value.length > 5) bits.push('…'); preview += ' [' + bits.join(', ') + ']'; } else { var keys = Object.keys(value); var bits2 = []; for (var j = 0; j < Math.min(keys.length, 4); j++) { bits2.push(keys[j] + ': ' + briefVal(value[keys[j]])); } if (keys.length > 4) bits2.push('…'); preview += ' {' + bits2.join(', ') + '}'; } } catch (e) { /* */ } return { kind: 'object', oid: oid, ctor: ctor, isArray: isArray, length: isArray ? value.length : undefined, preview: preview, plain: preview, }; }; WorkbenchAgent.prototype._inspectConsoleObj = function (oid, path) { var root = this._consoleObjs[oid]; if (!root) return { props: [] }; var target = root; var segs = String(path || '').split('.').filter(Boolean); for (var i = 0; i < segs.length; i++) { try { target = target[segs[i]]; } catch (e) { return { props: [] }; } } var props = []; var MAX = 40; try { if (Array.isArray(target)) { for (var a = 0; a < Math.min(target.length, MAX); a++) { props.push({ key: String(a), part: this._valuePart(target[a]) }); } if (target.length > MAX) props.push({ key: '…', part: { kind: 'text', text: '…', plain: '…' } }); } else if (target && typeof target === 'object') { var keys = Object.getOwnPropertyNames(target); for (var k = 0; k < Math.min(keys.length, MAX); k++) { var key = keys[k]; var child; try { child = target[key]; } catch (err) { child = '⟨access error⟩'; } props.push({ key: key, part: this._valuePart(child) }); } if (keys.length > MAX) props.push({ key: '…', part: { kind: 'text', text: '…', plain: '…' } }); } } catch (e2) { /* */ } return { props: props }; }; function briefVal(v) { if (v === null) return 'null'; var t = typeof v; if (t === 'string') return '"' + (v.length > 24 ? v.slice(0, 24) + '…' : v) + '"'; if (t === 'number' || t === 'boolean' || t === 'undefined') return String(v); if (t === 'function') return 'ƒ'; if (Array.isArray(v)) return 'Array(' + v.length + ')'; if (t === 'object') return '{…}'; return String(v); } WorkbenchAgent.prototype._setPaused = function (paused) { var cc = this.cc; if (!cc || !cc.director) return false; this._paused = !!paused; try { if (this._paused) cc.director.pause(); else cc.director.resume(); } catch (e) { return false; } return true; }; WorkbenchAgent.prototype._stepFrame = function () { var cc = this.cc; if (!cc) return false; try { if (!this._paused && cc.director) { cc.director.pause(); this._paused = true; } if (cc.game && typeof cc.game.step === 'function') { cc.game.step(); return true; } if (cc.director && typeof cc.director.tick === 'function') { var fps = (cc.game && cc.game.frameRate) ? cc.game.frameRate : 60; cc.director.tick(1 / Math.max(1, fps)); return true; } } catch (e) { console.warn('[Agent] step', e); } return false; }; WorkbenchAgent.prototype._setFps = function (on) { var cc = this.cc; this._fpsOn = !!on; if (!cc || !cc.profiler) return false; try { if (this._fpsOn) cc.profiler.showStats(); else cc.profiler.hideStats(); return true; } catch (e) { return false; } }; function nodeId(n) { return String(n.uuid || n._id || n.name || ''); } function walk(node, fn) { if (!node) return; fn(node); var ch = node.children; if (!ch) return; for (var i = 0; i < ch.length; i++) walk(ch[i], fn); } function serializeNode(node, depth, maxDepth) { if (!node || depth > maxDepth) return null; var children = []; var ch = node.children || []; for (var i = 0; i < ch.length; i++) { var c = serializeNode(ch[i], depth + 1, maxDepth); if (c) children.push(c); } var comps = []; if (node.components) { for (var j = 0; j < node.components.length; j++) { var comp = node.components[j]; if (comp && comp.constructor) comps.push(comp.constructor.name || ''); } } return { uuid: nodeId(node), name: node.name || '(未命名)', active: node.active !== false, comps: comps, children: children, }; } function copySeries(series) { var out = {}; for (var k in series) { if (Object.prototype.hasOwnProperty.call(series, k)) out[k] = series[k].slice(); } return out; } function hierFingerprint(node) { if (!node) return ''; var parts = [node.uuid, node.name, node.active ? 1 : 0, (node.comps || []).length]; var ch = node.children || []; parts.push(ch.length); for (var i = 0; i < ch.length; i++) parts.push(hierFingerprint(ch[i])); return parts.join('/'); } global.PWAgent = WorkbenchAgent; function startAgent() { if (global.__PW_MODE && global.__PW_MODE !== 'game') return; var agent = new WorkbenchAgent(); agent.boot(); global.__PW_Agent = agent; } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', startAgent); } else { startAgent(); } })(window);