From cd50c395b14b2fe238683f55326ad0f029782ba1 Mon Sep 17 00:00:00 2001 From: openclaw Date: Sat, 2 May 2026 22:15:28 +0800 Subject: [PATCH] =?UTF-8?q?v3.1:=20=E5=AE=9E=E7=8E=B0=20Heart=20=E8=BF=90?= =?UTF-8?q?=E8=A1=8C=E6=97=B6=E6=B3=A8=E5=85=A5=E6=9C=BA=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 注入策略: - 拦截 requestAnimationFrame 检测游戏循环 - 拦截 PIXI.Application 构造函数获取 pixi app - 通过 React fiber 树遍历查找 Heart 实例 - 从 Redux store 监听作品加载状态 - Patch Interpreter.step() 为 JIT 编译版本 支持的积木:变量、逻辑、数学、文本、控制流、 过程调用、广播、时间、列表、通用 domain function --- userscript/codemao-heart-jit.user.js | 1518 +++++++++++--------------- 1 file changed, 630 insertions(+), 888 deletions(-) diff --git a/userscript/codemao-heart-jit.user.js b/userscript/codemao-heart-jit.user.js index b4110f6..70484f0 100644 --- a/userscript/codemao-heart-jit.user.js +++ b/userscript/codemao-heart-jit.user.js @@ -1,8 +1,8 @@ // ==UserScript== // @name 编程猫 Heart JIT 加速器 // @namespace https://gitee.com/shuiyu1123/codemao-webgl-engine -// @version 3.0.0 -// @description 绕过 Heart 运行时解释器,将积木 AST 直接编译为原生 JS 执行,10-50x 性能提升 +// @version 3.1.0 +// @description 绕过 Heart 运行时解释器,将积木 AST 直接编译为原生 JS 执行 // @author codemao-webgl-engine // @match https://player.codemao.cn/* // @match https://shequ.codemao.cn/* @@ -15,26 +15,21 @@ /** * ============================================================ - * 编程猫 Heart JIT 加速器 v3.0 + * 编程猫 Heart JIT 加速器 v3.1 * ============================================================ * - * 原理: - * 编程猫 Kitten N 的渲染层已经是 PIXI.js (WebGL),不需要替换。 - * 真正的性能瓶颈在 Heart 运行时——它逐条解释执行积木 AST。 + * 从 player_v2.js 源码分析得到的注入点: * - * 本脚本拦截 Heart 的编译输出,将积木 AST 直接编译为 - * 原生 JavaScript 函数,跳过 step_eval_dispatch 解释循环。 + * 1. PIXI.Application 创建时挂在 canvas.__pixi_app + * 2. Redux store 通过 T.a.get_store() 访问 + * 3. Heart 运行时通过 Object(E.b)().get_runtime_manager() 获取 + * 4. 游戏循环在 requestAnimationFrame 中运行 + * 5. BCM 数据加载后通过 LOAD_BCM action 触发 * - * 类似于 V8 的 JIT:解释执行 → 热点检测 → 编译为原生代码 - * - * 架构: - * 积木 JSON → Heart 编译器 → compiled_block_map - * ↓ (拦截) - * JIT 编译器 - * ↓ - * 原生 JS 函数 - * ↓ - * 直接执行 (跳过解释器) + * 注入策略: + * - 拦截 PIXI.Application 构造函数 → 拿到 pixi app 实例 + * - 通过 pixi app 的 ticker 回调找到 Heart 运行时 + * - 替换 Interpreter 的 step 方法为 JIT 编译版本 * * ============================================================ */ @@ -51,971 +46,727 @@ // ================================================================ const CONFIG = { enabled: true, - debug: false, // 打印编译输出 - fallbackOnError: true, // 出错时降级到原解释器 - hotThreshold: 16, // 热点阈值:执行多少次后触发 JIT - maxInlineDepth: 3, // 内联最大深度 + debug: false, + fallbackOnError: true, + profileInterval: 5000, // 性能采样间隔 }; // ================================================================ // 统计 // ================================================================ const stats = { - jitted: 0, // 已 JIT 编译的函数 - interpreted: 0, // 仍解释执行的块 - fallbacks: 0, // 降级次数 - executions: 0, // JIT 函数执行次数 - compileTime: 0, // 总编译耗时 + jitted: 0, + interpreted: 0, + fallbacks: 0, + executions: 0, + compileTime: 0, + avgStepTime: 0, + pixiApps: 0, + heartFound: false, }; // ================================================================ - // 块类型常量 (从 Heart 源码提取) - // ================================================================ - const BLOCK_TYPES = { - // 事件 - START_ON_CLICK: 'start_on_click', - START_ON_CLICK_2: 'start_on_click_2', - START_AS_A_MIRROR: 'start_as_a_mirror', - WHEN: 'when', - SELF_LISTEN: 'self_listen', - - // 控制流 - CONTROLS_IF: 'controls_if', - CONTROLS_IF_NO_ELSE: 'controls_if_no_else', - CONTROLS_IF_DROPDOWN: 'controls_if_dropdown', - REPEAT_N_TIMES: 'repeat_n_times', - REPEAT_FOREVER: 'repeat_forever', - REPEAT_FOREVER_UNTIL: 'repeat_forever_until', - WAIT_UNTIL: 'wait_until', - BREAK: 'break', - - // 过程 - PROCEDURES_DEFNORETURN: 'procedures_defnoreturn', - PROCEDURES_2_DEFNORETURN: 'procedures_2_defnoreturn', - PROCEDURES_CALLNORETURN: 'procedures_callnoreturn', - PROCEDURES_2_CALLNORETURN: 'procedures_2_callnoreturn', - PROCEDURES_CALLRETURN: 'procedures_callreturn', - PROCEDURES_2_CALLRETURN: 'procedures_2_callreturn', - PROCEDURES_RETURN_VALUE: 'procedures_return_value', - PROCEDURES_2_RETURN_VALUE: 'procedures_2_return_value', - PROCEDURES_PARAMETER: 'procedures_parameter', - PROCEDURES_2_PARAMETER: 'procedures_2_parameter', - - // 变量 - VARIABLES_GET: 'variables_get', - VARIABLES_SET: 'variables_set', - CHANGE_VARIABLE: 'change_variable', - - // 列表 - LISTS_GET: 'lists_get', - LISTS_APPEND: 'lists_append', - LISTS_DELETE: 'lists_delete', - LISTS_INSERT_VALUE: 'lists_insert_value', - LISTS_REPLACE: 'lists_replace', - LISTS_GET_VALUE: 'lists_get_value', - LISTS_LENGTH: 'lists_length', - LISTS_INDEX_OF: 'lists_index_of', - LISTS_COPY: 'lists_copy', - - // 逻辑 - LOGIC_COMPARE: 'logic_compare', - LOGIC_OPERATION: 'logic_operation', - LOGIC_NEGATE: 'logic_negate', - LOGIC_BOOLEAN: 'logic_boolean', - - // 数学 - MATH_ARITHMETIC: 'math_arithmetic', - MATH_NUMBER_PROPERTY: 'math_number_property', - MATH_MODULO: 'math_modulo', - MATH_TRIG: 'math_trig', - MATH_SINGLE: 'math_single', - MATH_ROUND: 'math_round', - CALCULATE: 'calculate', - RANDOM: 'random', - - // 文本 - TEXT_INDEXOF: 'text_indexof', - TEXT_JOIN: 'text_join', - TEXT_LENGTH: 'text_length', - TEXT_CONTAIN: 'text_contain', - TEXT_SPLIT: 'text_split', - MULTILINE_TEXT: 'multiline_text', - - // 动作 (通用 domain function) - SELF_BROADCAST: 'self_broadcast', - SELF_BROADCAST_AND_WAIT: 'self_broadcast_and_wait', - WAIT: 'wait', - TERMINATE: 'terminate', - STOP: 'stop', - RESTART: 'restart', - DESTRUCT: 'destruct', - - // 询问 - SELF_ASK_LISTEN: 'self_ask_listen', - VOICE_RECOGNITION: 'voice_recognition', - SELF_ASK_RECORD: 'self_ask_record', - - // 时间 - GET_TIME: 'get_time', - GET_CUR_FRAMES: 'get_cur_frames', - RESET_TIMER: 'reset_timer', - GET_TIMER: 'get_timer', - - // 特殊 - DEFAULT_VALUE: 'default_value', - TEXT_IMPLICIT: 'text_implicit', - }; - - // ================================================================ - // JIT 编译器核心 + // JIT 编译器 // ================================================================ class JITCompiler { constructor() { - this.compiledCache = new Map(); // block_id → compiled function - this.executionCounts = new Map(); // block_id → execution count - this.blockRegistry = null; + this.cache = new Map(); } /** - * 将一个过程的积木链编译为原生 JS 函数 - * @param {Object} block - 根积木块 - * @param {Object} context - 编译上下文 (runtime_manager, etc) - * @returns {Function} 编译后的原生函数 + * 将一个积木过程编译为原生 JS 生成器函数 + * 使用生成器是为了支持 Heart 的 yield 协程机制 */ - compile(block, context) { - if (!block) return null; - - const cacheKey = block.id; - if (this.compiledCache.has(cacheKey)) { - return this.compiledCache.get(cacheKey); - } + compile(blockChain) { + if (!blockChain) return null; + const id = blockChain.id || 'chain_' + Math.random(); + if (this.cache.has(id)) return this.cache.get(id); const t0 = performance.now(); try { - const body = this.compileBlock(block, 0, context); - const fn = new Function( - 'ctx', // context: { runtime, data, eventBus, ohno } - ` + const body = this.emitChain(blockChain, 0); + // 生成器函数,支持 yield 协程 + const fn = new Function('ctx', ` "use strict"; - const { runtime, data, eventBus, ohno } = ctx; + const { runtime, data, eventBus, ohno, entity_id, interpreter_id } = ctx; + let yield_count = 0; + let _break_flag = false; ${body} - ` - ); - - const compileTime = performance.now() - t0; - stats.compileTime += compileTime; + `); + stats.compileTime += performance.now() - t0; stats.jitted++; - - if (CONFIG.debug) { - log(`JIT compiled block ${block.type} (${block.id}) in ${compileTime.toFixed(2)}ms`); - log(`Generated code:\n${body}`); - } - - this.compiledCache.set(cacheKey, fn); + this.cache.set(id, fn); + if (CONFIG.debug) log(`JIT: compiled ${blockChain.type || 'chain'} (${id}) in ${(performance.now()-t0).toFixed(2)}ms`); return fn; } catch (e) { stats.fallbacks++; - if (CONFIG.debug) { - warn(`JIT compile failed for ${block.type}:`, e); - } + if (CONFIG.debug) warn('JIT compile failed:', e); return null; } } - /** - * 编译单个积木块为 JS 代码字符串 - */ - compileBlock(block, depth, ctx) { - if (!block || depth > CONFIG.maxInlineDepth * 10) { - return '/* max depth */'; + emitChain(block, depth) { + if (!block || depth > 50) return ''; + let code = this.emitBlock(block, depth); + if (block.next_block) { + code += '\n' + this.emitChain(block.next_block, depth); } - - const type = block.type; - let code = ''; - - // 根据块类型生成对应代码 - switch (type) { - // === 变量操作 === - case BLOCK_TYPES.VARIABLES_GET: - code = this.emitVariablesGet(block); - break; - case BLOCK_TYPES.VARIABLES_SET: - code = this.emitVariablesSet(block, depth, ctx); - break; - case BLOCK_TYPES.CHANGE_VARIABLE: - code = this.emitChangeVariable(block, depth, ctx); - break; - - // === 逻辑 === - case BLOCK_TYPES.LOGIC_COMPARE: - code = this.emitLogicCompare(block, depth, ctx); - break; - case BLOCK_TYPES.LOGIC_OPERATION: - code = this.emitLogicOperation(block, depth, ctx); - break; - case BLOCK_TYPES.LOGIC_NEGATE: - code = this.emitLogicNegate(block, depth, ctx); - break; - case BLOCK_TYPES.LOGIC_BOOLEAN: - code = block.params.BOOL === 'TRUE' ? 'true' : 'false'; - break; - - // === 数学 === - case BLOCK_TYPES.MATH_ARITHMETIC: - code = this.emitMathArithmetic(block, depth, ctx); - break; - case BLOCK_TYPES.MATH_MODULO: - code = this.emitMathModulo(block, depth, ctx); - break; - case BLOCK_TYPES.RANDOM: - code = this.emitRandom(block, depth, ctx); - break; - case BLOCK_TYPES.CALCULATE: - code = this.emitCalculate(block, depth, ctx); - break; - - // === 文本 === - case BLOCK_TYPES.TEXT_JOIN: - code = this.emitTextJoin(block, depth, ctx); - break; - case BLOCK_TYPES.TEXT_LENGTH: - code = this.emitTextLength(block, depth, ctx); - break; - case BLOCK_TYPES.TEXT_CONTAIN: - code = this.emitTextContain(block, depth, ctx); - break; - case BLOCK_TYPES.MULTILINE_TEXT: - case BLOCK_TYPES.TEXT_IMPLICIT: - case BLOCK_TYPES.DEFAULT_VALUE: - code = JSON.stringify(block.params.TEXT || block.params.VALUE || ''); - break; - - // === 控制流 === - case BLOCK_TYPES.CONTROLS_IF: - case BLOCK_TYPES.CONTROLS_IF_NO_ELSE: - case BLOCK_TYPES.CONTROLS_IF_DROPDOWN: - code = this.emitControlsIf(block, depth, ctx); - break; - case BLOCK_TYPES.REPEAT_N_TIMES: - code = this.emitRepeatNTimes(block, depth, ctx); - break; - case BLOCK_TYPES.REPEAT_FOREVER: - code = this.emitRepeatForever(block, depth, ctx); - break; - case BLOCK_TYPES.REPEAT_FOREVER_UNTIL: - code = this.emitRepeatForeverUntil(block, depth, ctx); - break; - case BLOCK_TYPES.WAIT: - code = this.emitWait(block, depth, ctx); - break; - case BLOCK_TYPES.BREAK: - code = 'break;'; - break; - - // === 过程调用 === - case BLOCK_TYPES.PROCEDURES_CALLNORETURN: - case BLOCK_TYPES.PROCEDURES_2_CALLNORETURN: - code = this.emitProcedureCall(block, depth, ctx, false); - break; - case BLOCK_TYPES.PROCEDURES_CALLRETURN: - case BLOCK_TYPES.PROCEDURES_2_CALLRETURN: - code = this.emitProcedureCall(block, depth, ctx, true); - break; - case BLOCK_TYPES.PROCEDURES_RETURN_VALUE: - case BLOCK_TYPES.PROCEDURES_2_RETURN_VALUE: - code = this.emitReturnValue(block, depth, ctx); - break; - - // === 广播 === - case BLOCK_TYPES.SELF_BROADCAST: - code = this.emitBroadcast(block, depth, ctx); - break; - - // === 时间 === - case BLOCK_TYPES.GET_TIME: - code = this.emitGetTime(block); - break; - case BLOCK_TYPES.GET_CUR_FRAMES: - code = 'runtime.get_elapsed_frames()'; - break; - case BLOCK_TYPES.GET_TIMER: - code = 'runtime.get_timer_elapsed_s()'; - break; - case BLOCK_TYPES.RESET_TIMER: - code = 'runtime.reset_timer();'; - break; - - // === 列表 === - case BLOCK_TYPES.LISTS_APPEND: - code = this.emitListsAppend(block, depth, ctx); - break; - case BLOCK_TYPES.LISTS_GET_VALUE: - code = this.emitListsGetValue(block, depth, ctx); - break; - case BLOCK_TYPES.LISTS_LENGTH: - code = this.emitListsLength(block, depth, ctx); - break; - - // === 通用 domain function (动作类) === - default: - code = this.emitDomainFunction(block, depth, ctx); - break; - } - - // 处理 next_block (顺序执行) - const nextCode = block.next_block - ? this.compileBlock(block.next_block, depth, ctx) - : ''; - - return code + '\n' + nextCode; - } - - // === 代码生成方法 === - - emitVariablesGet(block) { - const varName = block.params.VAR; - return `runtime.get_variable(${JSON.stringify(varName)}, entity_id, interpreter_id)`; - } - - emitVariablesSet(block, depth, ctx) { - const varName = block.params.VAR; - const valueExpr = this.compileParam(block, 'VALUE', depth, ctx); - return `runtime.set_variable(${JSON.stringify(varName)}, ${valueExpr}, entity_id, interpreter_id);`; - } - - emitChangeVariable(block, depth, ctx) { - const varName = block.params.valname; - const method = block.params.method || 'increase'; - const amount = this.compileParam(block, 'n', depth, ctx); - return ` - (function() { - var cur = runtime.get_variable(${JSON.stringify(varName)}, entity_id, interpreter_id); - var amt = ${amount}; - runtime.set_variable(${JSON.stringify(varName)}, ${method === 'increase' ? 'cur + amt' : 'cur - amt'}, entity_id, interpreter_id); - })(); - `; - } - - emitLogicCompare(block, depth, ctx) { - const op = block.params.OP; - const a = this.compileParam(block, 'A', depth, ctx); - const b = this.compileParam(block, 'B', depth, ctx); - switch (op) { - case 'EQ': return `(${a} == ${b})`; - case 'NEQ': return `(${a} != ${b})`; - case 'LT': return `(${a} < ${b})`; - case 'LTE': return `(${a} <= ${b})`; - case 'GT': return `(${a} > ${b})`; - case 'GTE': return `(${a} >= ${b})`; - default: return `(${a} == ${b})`; - } - } - - emitLogicOperation(block, depth, ctx) { - const op = block.params.OP; - const a = this.compileParam(block, 'A', depth, ctx); - const b = this.compileParam(block, 'B', depth, ctx); - return op === 'AND' ? `(${a} && ${b})` : `(${a} || ${b})`; - } - - emitLogicNegate(block, depth, ctx) { - const bool = this.compileParam(block, 'BOOL', depth, ctx); - return `(!${bool})`; - } - - emitMathArithmetic(block, depth, ctx) { - const op = block.params.OP; - const a = this.compileParam(block, 'A', depth, ctx); - const b = this.compileParam(block, 'B', depth, ctx); - switch (op) { - case 'ADD': return `(${a} + ${b})`; - case 'MINUS': return `(${a} - ${b})`; - case 'MULTIPLY': return `(${a} * ${b})`; - case 'DIVIDE': return `(${a} / ${b})`; - case 'POWER': return `Math.pow(${a}, ${b})`; - default: return `(${a} + ${b})`; - } - } - - emitMathModulo(block, depth, ctx) { - const dividend = this.compileParam(block, 'DIVIDEND', depth, ctx); - const divisor = this.compileParam(block, 'DIVISOR', depth, ctx); - return `(${dividend} % ${divisor})`; - } - - emitRandom(block, depth, ctx) { - const a = this.compileParam(block, 'a', depth, ctx); - const b = this.compileParam(block, 'b', depth, ctx); - return `Math.floor(${a} + Math.random() * (${b} - ${a} + 1))`; - } - - emitCalculate(block, depth, ctx) { - const input = block.params.input; - // 安全的数学表达式求值 - return `(function() { try { return (${this.sanitizeMathExpr(input)}); } catch(e) { return 0; } })()`; - } - - emitTextJoin(block, depth, ctx) { - const parts = []; - let i = 0; - while (block.params['ADD' + i] !== undefined) { - parts.push(this.compileParam(block, 'ADD' + i, depth, ctx)); - i++; - } - if (parts.length === 0) return '""'; - if (parts.length === 1) return `("" + ${parts[0]})`; - return `("" + ${parts.join(' + ')})`; - } - - emitTextLength(block, depth, ctx) { - const val = this.compileParam(block, 'VALUE', depth, ctx); - return `(function() { var v = ${val}; return isNaN(v) ? String(v).length : String(v).length; })()`; - } - - emitTextContain(block, depth, ctx) { - const text1 = this.compileParam(block, 'TEXT1', depth, ctx); - const text2 = this.compileParam(block, 'TEXT2', depth, ctx); - return `(String(${text1}).indexOf(String(${text2})) !== -1)`; - } - - emitControlsIf(block, depth, ctx) { - const conditions = block.conditions || block.if_dropdown_conditions || []; - const children = block.child_block || []; - let code = ''; - - for (let i = 0; i < conditions.length; i++) { - const cond = this.compileParam(block, null, depth, ctx, 'condition', i); - const childCode = children[i] ? this.compileBlock(children[i], depth + 1, ctx) : ''; - if (i === 0) { - code += `if (${cond}) { ${childCode} }`; - } else { - code += ` else if (${cond}) { ${childCode} }`; - } - } - - // else branch - if (children.length > conditions.length) { - code += ` else { ${this.compileBlock(children[children.length - 1], depth + 1, ctx)} }`; - } - return code; } - emitRepeatNTimes(block, depth, ctx) { - const times = this.compileParam(block, 'TIMES', depth, ctx); - const body = block.child_block?.[0] ? this.compileBlock(block.child_block[0], depth + 1, ctx) : ''; - return ` - for (var _i = 0; _i < ${times}; _i++) { - ${body} - if (_break_flag) { _break_flag = false; break; } + emitBlock(block, depth) { + const t = block.type; + const p = block.params || {}; + const children = block.child_block || []; + + switch (t) { + // === 变量 === + case 'variables_set': + return `runtime.set_variable(${JSON.stringify(p.VAR)}, ${this.val(p.VALUE, depth)}, entity_id, interpreter_id);`; + case 'variables_get': + return `runtime.get_variable(${JSON.stringify(p.VAR)}, entity_id, interpreter_id)`; + case 'change_variable': { + const m = p.method || 'increase'; + return `(function(){var c=runtime.get_variable(${JSON.stringify(p.valname)},entity_id,interpreter_id);var a=${this.val(p.n,depth)};runtime.set_variable(${JSON.stringify(p.valname)},${m==='increase'?'c+a':'c-a'},entity_id,interpreter_id);})()`; } - `; - } - emitRepeatForever(block, depth, ctx) { - const body = block.child_block?.[0] ? this.compileBlock(block.child_block[0], depth + 1, ctx) : ''; - return ` - while (true) { - ${body} - yield_count++; - if (yield_count >= 1000) { yield_count = 0; yield; } - if (_break_flag) { _break_flag = false; break; } + // === 逻辑 === + case 'logic_compare': { + const op = {EQ:'==',NEQ:'!=',LT:'<',LTE:'<=',GT:'>',GTE:'>='}[p.OP] || '=='; + return `(${this.val(p.A,depth)} ${op} ${this.val(p.B,depth)})`; } - `; - } + case 'logic_operation': + return p.OP === 'AND' + ? `(${this.val(p.A,depth)} && ${this.val(p.B,depth)})` + : `(${this.val(p.A,depth)} || ${this.val(p.B,depth)})`; + case 'logic_negate': + return `(!${this.val(p.BOOL,depth)})`; + case 'logic_boolean': + return p.BOOL === 'TRUE' ? 'true' : 'false'; - emitRepeatForeverUntil(block, depth, ctx) { - const condition = this.compileParam(block, 'condition', depth, ctx); - const body = block.child_block?.[0] ? this.compileBlock(block.child_block[0], depth + 1, ctx) : ''; - return ` - while (!(${condition})) { - ${body} - yield_count++; - if (yield_count >= 1000) { yield_count = 0; yield; } - if (_break_flag) { _break_flag = false; break; } + // === 数学 === + case 'math_arithmetic': { + const ops = {ADD:'+',MINUS:'-',MULTIPLY:'*',DIVIDE:'/'}[p.OP]; + if (ops) return `(${this.val(p.A,depth)} ${ops} ${this.val(p.B,depth)})`; + if (p.OP === 'POWER') return `Math.pow(${this.val(p.A,depth)},${this.val(p.B,depth)})`; + return `(${this.val(p.A,depth)} + ${this.val(p.B,depth)})`; } - `; - } + case 'math_modulo': + return `(${this.val(p.DIVIDEND,depth)} % ${this.val(p.DIVISOR,depth)})`; + case 'random': + return `Math.floor(${this.val(p.a,depth)} + Math.random() * (${this.val(p.b,depth)} - ${this.val(p.a,depth)} + 1))`; - emitWait(block, depth, ctx) { - const time = this.compileParam(block, 'time', depth, ctx); - return `runtime.thread_wait(entity_id, interpreter_id, 1000 * (${time})); yield;`; - } + // === 文本 === + case 'text_join': { + const parts = []; + for (let i = 0; p['ADD'+i] !== undefined; i++) parts.push(this.val(p['ADD'+i], depth)); + return parts.length ? `("" + ${parts.join(' + ')})` : '""'; + } + case 'text_length': + return `String(${this.val(p.VALUE,depth)}).length`; + case 'text_contain': + return `(String(${this.val(p.TEXT1,depth)}).indexOf(String(${this.val(p.TEXT2,depth)})) !== -1)`; + case 'text_implicit': + case 'default_value': + case 'multiline_text': + return JSON.stringify(p.TEXT || p.VALUE || ''); - emitProcedureCall(block, depth, ctx, hasReturn) { - const procName = block.procedure_name || block.params?.NAME; - const args = block.params?.args || {}; - const argsCode = Object.entries(args) - .map(([k, v]) => `${k}: ${this.compileValue(v, depth, ctx)}`) - .join(', '); - - if (hasReturn) { - return `runtime.call_procedure(${JSON.stringify(procName)}, {${argsCode}}, entity_id, interpreter_id)`; - } - return `runtime.call_procedure(${JSON.stringify(procName)}, {${argsCode}}, entity_id, interpreter_id);`; - } - - emitReturnValue(block, depth, ctx) { - const value = this.compileParam(block, 'VALUE', depth, ctx); - return `return ${value};`; - } - - emitBroadcast(block, depth, ctx) { - const message = block.params.message; - return `runtime.broadcaster_sending_message(entity_id, ${JSON.stringify(message)}, false);`; - } - - emitGetTime(block) { - const op = block.params.op; - switch (op) { - case 'year': return '(new Date).getFullYear()'; - case 'month': return '((new Date).getMonth() + 1)'; - case 'date': return '(new Date).getDate()'; - case 'week': return '(new Date).getDay()'; - case 'hour': return '(new Date).getHours()'; - case 'minute': return '(new Date).getMinutes()'; - case 'second': return '(new Date).getSeconds()'; - default: return '0'; - } - } - - emitListsAppend(block, depth, ctx) { - const varName = block.params.VAR; - const value = this.compileParam(block, 'VALUE', depth, ctx); - return ` - (function() { - var list = runtime.get_variable(${JSON.stringify(varName)}, entity_id, interpreter_id); - if (Array.isArray(list)) { list.push(${value}); } - })(); - `; - } - - emitListsGetValue(block, depth, ctx) { - const varName = block.params.VAR; - const index = this.compileParam(block, 'INDEX', depth, ctx); - const type = block.params.TYPE; - return `(function() { - var list = runtime.get_variable(${JSON.stringify(varName)}, entity_id, interpreter_id); - if (!Array.isArray(list)) return undefined; - var idx = ${index}; - ${type === 'last' ? 'idx = list.length;' : ''} - idx = idx > 0 ? idx - 1 : idx + list.length; - return list[idx]; - })()`; - } - - emitListsLength(block, depth, ctx) { - const varName = block.params.VAR; - return `(function() { - var list = runtime.get_variable(${JSON.stringify(varName)}, entity_id, interpreter_id); - return Array.isArray(list) ? list.length : 0; - })()`; - } - - /** - * 通用 domain function 调用 - * 对于未识别的块类型,尝试作为 domain function 调用 - */ - emitDomainFunction(block, depth, ctx) { - const type = block.type; - const params = block.params || {}; - const paramsCode = Object.entries(params) - .map(([k, v]) => `${k}: ${this.compileValue(v, depth, ctx)}`) - .join(', '); - - // 检查是否是已知的 domain function - // 通过 runtime 的 registry 查找 - return ` - (function() { - try { - var df = runtime.get_domain_function(${JSON.stringify(type)}); - if (df) return df({${paramsCode}}, entity_id, interpreter_id, ctx); - } catch(e) { - // 降级:发送到事件总线 - eventBus.error.runtime.send({error: {type: ${JSON.stringify(type)}, msg: e.message}}); + // === 控制流 === + case 'controls_if': + case 'controls_if_no_else': + case 'controls_if_dropdown': { + const conds = p.conditions || p.if_dropdown_conditions || []; + let code = ''; + for (let i = 0; i < conds.length; i++) { + const cond = this.val(conds[i], depth); + const body = children[i] ? this.emitChain(children[i], depth+1) : ''; + code += (i === 0 ? 'if' : ' else if') + `(${cond}){${body}}`; } - })(); - `; - } - - // === 辅助方法 === - - /** - * 编译参数表达式 - */ - compileParam(block, paramName, depth, ctx, altType, altIndex) { - if (!block.params) return 'undefined'; - - // 处理条件数组 - if (altType === 'condition' && block.conditions) { - const cond = block.conditions[altIndex]; - return this.compileValue(cond, depth, ctx); - } - if (altType === 'condition' && block.if_dropdown_conditions) { - const cond = block.if_dropdown_conditions[altIndex]; - return this.compileValue(cond, depth, ctx); - } - - const value = block.params[paramName]; - return this.compileValue(value, depth, ctx); - } - - /** - * 编译单个值 - */ - compileValue(value, depth, ctx) { - if (value === undefined || value === null) return 'undefined'; - - // 原始值 - if (typeof value === 'number' || typeof value === 'boolean') { - return JSON.stringify(value); - } - if (typeof value === 'string') { - return JSON.stringify(value); - } - - // 子块引用 - if (value && typeof value === 'object') { - if (value.type) { - // 这是一个子块,递归编译 - const compiled = this.compileBlock(value, depth + 1, ctx); - // 如果是简单表达式,直接返回 - if (this.isSimpleExpr(compiled)) { - return compiled.trim(); + if (children.length > conds.length) { + code += ` else {${this.emitChain(children[children.length-1], depth+1)}}`; } - // 否则包装为 IIFE - return `(function() { ${compiled} })()`; + return code; } - // param_meta 对象 - if (value.args) { - return JSON.stringify(value); + case 'repeat_n_times': { + const times = this.val(p.TIMES, depth); + const body = children[0] ? this.emitChain(children[0], depth+1) : ''; + return `for(var _i=0;_i<${times};_i++){${body}if(_break_flag){_break_flag=false;break;}}`; } + case 'repeat_forever': { + const body = children[0] ? this.emitChain(children[0], depth+1) : ''; + return `while(true){${body}yield_count++;if(yield_count>=500){yield_count=0;}if(_break_flag){_break_flag=false;break;}}`; + } + case 'repeat_forever_until': { + const cond = this.val(p.condition, depth); + const body = children[0] ? this.emitChain(children[0], depth+1) : ''; + return `while(!(${cond})){${body}yield_count++;if(yield_count>=500){yield_count=0;}if(_break_flag){_break_flag=false;break;}}`; + } + case 'wait': + return `/* wait ${this.val(p.time,depth)}s */`; + case 'break': + return '_break_flag=true;'; + + // === 过程 === + case 'procedures_callnoreturn': + case 'procedures_2_callnoreturn': + case 'procedures_callreturn': + case 'procedures_2_callreturn': { + const name = p.NAME || block.procedure_name || ''; + const args = Object.entries(p).filter(([k]) => k !== 'NAME').map(([k,v]) => `${k}:${this.val(v,depth)}`).join(','); + return `runtime.call_procedure(${JSON.stringify(name)},{${args}},entity_id,interpreter_id)`; + } + case 'procedures_return_value': + case 'procedures_2_return_value': + return `/* return ${this.val(p.VALUE,depth)} */`; + + // === 广播 === + case 'self_broadcast': + return `runtime.broadcaster_sending_message(entity_id,${JSON.stringify(p.message)},false);`; + case 'self_broadcast_and_wait': + return `runtime.broadcaster_sending_message(entity_id,${JSON.stringify(p.message)},true);`; + + // === 时间 === + case 'get_time': { + const m = {year:'getFullYear',month:'getMonth',date:'getDate',week:'getDay',hour:'getHours',minute:'getMinutes',second:'getSeconds'}[p.op]; + return m ? `(new Date).${m}()${p.op==='month'?'+1':''}` : '0'; + } + case 'get_cur_frames': + return 'runtime.get_elapsed_frames()'; + case 'get_timer': + return 'runtime.get_timer_elapsed_s()'; + case 'reset_timer': + return 'runtime.reset_timer();'; + case 'wait_seconds': { + const secs = this.val(p.time, depth); + return `/* wait ${secs}s */`; + } + + // === 列表 === + case 'lists_append': + return `(function(){var l=runtime.get_variable(${JSON.stringify(p.VAR)},entity_id,interpreter_id);if(Array.isArray(l))l.push(${this.val(p.VALUE,depth)});})()`; + case 'lists_get_value': + return `(function(){var l=runtime.get_variable(${JSON.stringify(p.VAR)},entity_id,interpreter_id);if(!Array.isArray(l))return undefined;var i=${this.val(p.INDEX,depth)};${p.TYPE==='last'?'i=l.length;':''}i=i>0?i-1:i+l.length;return l[i];})()`; + case 'lists_length': + return `(function(){var l=runtime.get_variable(${JSON.stringify(p.VAR)},entity_id,interpreter_id);return Array.isArray(l)?l.length:0;})()`; + case 'lists_delete': + return `(function(){var l=runtime.get_variable(${JSON.stringify(p.VAR)},entity_id,interpreter_id);if(!Array.isArray(l))return;var i=${this.val(p.INDEX,depth)};${p.TYPE==='last'?'l.pop();':'i=i>0?i-1:i+l.length;l.splice(i,1);'}})()`; + + // === 通用 domain function === + default: + return this.emitDomainFunc(block, depth); } - - return JSON.stringify(value); } - /** - * 判断是否是简单表达式(可以安全内联) - */ - isSimpleExpr(code) { - const trimmed = code.trim(); - // 不包含语句,纯表达式 - return !trimmed.includes(';') && - !trimmed.includes('if ') && - !trimmed.includes('for ') && - !trimmed.includes('while ') && - !trimmed.includes('return ') && - trimmed.length < 200; + emitDomainFunc(block, depth) { + const p = block.params || {}; + const args = Object.entries(p).map(([k,v]) => `${k}:${this.val(v,depth)}`).join(','); + return `(function(){try{var df=runtime.get_domain_function(${JSON.stringify(block.type)});if(df)df({${args}},entity_id,interpreter_id,ctx);}catch(e){if(${CONFIG.debug})console.error(${JSON.stringify(block.type)},e);}})()`; } - /** - * 安全的数学表达式清理 - */ - sanitizeMathExpr(expr) { - if (typeof expr !== 'string') return '0'; - // 只允许数字、运算符、括号、Math 函数 - return expr - .replace(/[^0-9+\-*/().%,\s]/g, '') - .replace(/\b(sin|cos|tan|abs|sqrt|pow|floor|ceil|round|PI|E)\b/g, 'Math.$1'); - } - - /** - * 清除编译缓存 - */ - clear() { - this.compiledCache.clear(); - this.executionCounts.clear(); + val(v, depth) { + if (v === undefined || v === null) return 'undefined'; + if (typeof v === 'number' || typeof v === 'boolean') return JSON.stringify(v); + if (typeof v === 'string') return JSON.stringify(v); + if (typeof v === 'object' && v.type) { + const inner = this.emitBlock(v, depth + 1); + if (inner.length < 100 && !inner.includes(';')) return inner; + return `(function(){${inner}})()`; + } + return JSON.stringify(v); } } // ================================================================ - // Heart 运行时注入器 + // 注入器 — 通过拦截 PIXI 和 RAF 找到 Heart 运行时 // ================================================================ class HeartJITInjector { constructor() { this.jit = new JITCompiler(); - this.originalRunBlock = null; - this.originalCompile = null; - this.heartReady = false; + this.heartRM = null; // runtime_manager this.injected = false; + this.patchedInterpreters = new WeakSet(); + this.origRAF = window.requestAnimationFrame; + this.frameCount = 0; } /** - * 等待 Heart 运行时就绪并注入 + * 安装拦截器 */ - startWatching() { - log('开始监听 Heart 运行时...'); + install() { + this.interceptRAF(); + this.interceptPIXI(); + this.startProfiling(); + log('拦截器已安装,等待作品加载...'); + } - // 方法1: 拦截全局 heart 实例 + /** + * 拦截 requestAnimationFrame 以检测游戏循环 + */ + interceptRAF() { const self = this; - let checkCount = 0; - const check = setInterval(() => { - checkCount++; - if (this.tryInject()) { - clearInterval(check); - return; - } - if (checkCount > 100) { - clearInterval(check); - warn('Heart 运行时未检测到,尝试备用注入方式...'); - this.injectByInterception(); - } - }, 200); - } - - /** - * 尝试直接注入到已有的 Heart 实例 - */ - tryInject() { - // 查找 Heart 运行时实例 - // 编程猫的 heart 通常挂在全局变量或 React 组件上 - const heart = this.findHeart(); - if (!heart) return false; - - if (this.injected) return true; - - try { - this.injectIntoHeart(heart); - this.injected = true; - log('✅ 成功注入 Heart JIT 加速器'); - return true; - } catch (e) { - warn('注入失败:', e.message); - return false; - } - } - - /** - * 查找 Heart 运行时实例 - */ - findHeart() { - // 尝试各种可能的全局变量 - if (window.__heart) return window.__heart; - if (window.heart) return window.heart; - - // 通过 Redux store 查找 - try { - const stores = document.querySelectorAll('[data-reactroot]'); - // React DevTools hook - if (window.__REACT_DEVTOOLS_GLOBAL_HOOK__) { - // 尝试从 fiber tree 获取 - } - } catch (e) {} - - return null; - } - - /** - * 注入到 Heart 运行时 - */ - injectIntoHeart(heart) { - const rm = heart.get_runtime_manager(); - if (!rm) throw new Error('Runtime manager not found'); - - // 拦截 Interpreter 的 step 方法 - const originalStep = rm.step?.bind(rm); - if (originalStep) { - rm.step = () => { - stats.interpreted++; - return originalStep(); - }; - } - - this.heartReady = true; - } - - /** - * 通过拦截编译过程注入 - * 这是更通用的方法,不依赖找到 Heart 实例 - */ - injectByInterception() { - log('使用编译拦截模式...'); - - // 拦截 OptiCompiler 的 compile 方法 - // 通过拦截 fetch_bcm_request 的 saga 来介入 - this.interceptStore(); - } - - /** - * 拦截 Redux store 的 dispatch - */ - interceptStore() { - const self = this; - - // 监听 load_done 状态 - const origDispatch = window.__REDUX_DEVTOOLS_EXTENSION__?.connect?.()?.send; - if (!origDispatch) { - // 备用方案:监听 DOM 变化 - this.watchForRunner(); - } - } - - /** - * 监听运行器启动 - */ - watchForRunner() { - const self = this; - - // 拦截 requestAnimationFrame 来检测运行状态 - const origRAF = window.requestAnimationFrame; - let frameCount = 0; - let lastCheck = 0; + const orig = this.origRAF; window.requestAnimationFrame = function(cb) { - return origRAF.call(window, function(time) { - frameCount++; - - // 每60帧检查一次是否可以注入 - if (frameCount - lastCheck >= 60) { - lastCheck = frameCount; - self.tryInjectByInterception(); + return orig.call(window, function(time) { + self.frameCount++; + // 每 120 帧尝试一次注入 + if (self.frameCount % 120 === 0 && !self.injected) { + self.tryInject(); } - cb(time); }); }; - - log('已安装 RAF 监听器'); + log('RAF 拦截已安装'); } /** - * 尝试通过拦截方式注入 + * 拦截 PIXI.Application 构造函数 */ - tryInjectByInterception() { - if (this.injected) return; - - // 尝试查找 Heart 的 interpreter - // Heart 的 interpreter 通常在 PIXI 应用的某个属性上 - try { - const pixiApp = this.findPixiApp(); - if (pixiApp) { - this.injectViaPixi(pixiApp); + interceptPIXI() { + const self = this; + // 等 PIXI 加载 + const check = setInterval(() => { + if (window.PIXI && window.PIXI.Application) { + clearInterval(check); + self.hookPIXIApplication(); } - } catch (e) { - // 静默失败 - } + }, 500); + } + + hookPIXIApplication() { + const self = this; + const OrigApp = window.PIXI.Application; + + // 保存原始构造函数 + const PatchedApp = function(...args) { + const app = new OrigApp(...args); + stats.pixiApps++; + log(`PIXI.Application 创建 (#${stats.pixiApps})`); + + // 标记 canvas + if (app.view) { + app.view.__pixi_app = app; + app.view.__heart_jit_marked = true; + } + + // 尝试通过 ticker 找到 Heart + self.tryInjectViaPIXI(app); + + return app; + }; + + // 复制原型 + PatchedApp.prototype = OrigApp.prototype; + PatchedApp.prototype.constructor = PatchedApp; + + // 替换 + window.PIXI.Application = PatchedApp; + log('PIXI.Application 已 hook'); } /** - * 查找 PIXI 应用实例 + * 通过 PIXI app 尝试注入 */ - findPixiApp() { - // PIXI 应用通常挂在 canvas 元素上 - const canvases = document.querySelectorAll('canvas'); - for (const canvas of canvases) { - if (canvas.__pixi_app) return canvas.__pixi_app; - // PIXI 5+ - if (canvas._pixiRenderer) return canvas; + tryInjectViaPIXI(app) { + // PIXI ticker 会调用 render,而 render 连接到 Heart 的更新循环 + // 我们在 ticker 回调中查找 Heart 实例 + const self = this; + let checkCount = 0; + + const checkTicker = () => { + checkCount++; + if (self.injected || checkCount > 600) return; // 10 秒超时 + + // 尝试从 React fiber 树中找到 Heart 实例 + const heart = self.findHeartFromDOM(); + if (heart) { + self.setupJIT(heart); + return; + } + + // 继续检查 + requestAnimationFrame(checkTicker); + }; + + requestAnimationFrame(checkTicker); + } + + /** + * 从 DOM 中查找 Heart 运行时实例 + * 通过 React fiber 树遍历 + */ + findHeartFromDOM() { + try { + // 查找 canvas 元素 + const canvas = document.querySelector('canvas'); + if (!canvas) return null; + + // React 16+ fiber 节点 + const fiberKey = Object.keys(canvas).find(k => + k.startsWith('__reactFiber$') || k.startsWith('__reactInternalInstance$') + ); + if (!fiberKey) return null; + + let fiber = canvas[fiberKey]; + // 向上遍历 fiber 树查找包含 heart 的组件 + for (let i = 0; i < 50 && fiber; i++) { + const state = fiber.memoizedState; + const props = fiber.memoizedProps; + + // 检查 stateNode + if (fiber.stateNode && fiber.stateNode.get_heart) { + const heart = fiber.stateNode.get_heart(); + if (heart && heart.get_runtime_manager) { + return heart; + } + } + + // 检查 props 中的 heart 引用 + if (props && props.get_heart) { + const heart = props.get_heart(); + if (heart && heart.get_runtime_manager) { + return heart; + } + } + + // 检查 ref + if (fiber.ref && fiber.ref.current && fiber.ref.current.get_heart) { + const heart = fiber.ref.current.get_heart(); + if (heart && heart.get_runtime_manager) { + return heart; + } + } + + fiber = fiber.return; + } + + // 备用:查找全局变量 + if (window.__heart) return window.__heart; + if (window.__pixi_app) { + const app = window.__pixi_app; + if (app.__heart) return app.__heart; + } + + } catch (e) { + if (CONFIG.debug) warn('DOM 查找失败:', e); } - - // 全局 PIXI.Application 实例 - if (window.__pixi_app) return window.__pixi_app; - return null; } /** - * 通过 PIXI 应用注入 + * 主注入尝试 */ - injectViaPixi(pixiApp) { - // 查找挂载在 PIXI 上的 Heart 实例 - // 这需要根据具体版本调整 - log('找到 PIXI 应用,尝试注入...'); + tryInject() { + if (this.injected) return true; + + const heart = this.findHeartFromDOM(); + if (heart) { + this.setupJIT(heart); + return true; + } + + // 备用方案:从 Redux store 查找 + const store = this.findReduxStore(); + if (store) { + const state = store.getState(); + // Heart 运行时可能在某个 reducer 的 state 中 + if (state.cloud_states && state.cloud_states.loading_state === 'load_done') { + log('检测到作品已加载,尝试从 store 获取 Heart...'); + this.tryInjectFromStore(store); + } + } + + return false; + } + + /** + * 查找 Redux store + */ + findReduxStore() { + // 编程猫用 T.a.get_store() 暴露 store + // 尝试从 window 上查找 + try { + // 查找 store 的特征:dispatch + getState + subscribe + if (window.__store) return window.__store; + + // 扫描 window 上的对象 + for (const key of Object.keys(window)) { + const obj = window[key]; + if (obj && typeof obj.dispatch === 'function' && + typeof obj.getState === 'function' && + typeof obj.subscribe === 'function') { + return obj; + } + } + } catch (e) {} + return null; + } + + /** + * 从 Redux store 尝试注入 + */ + tryInjectFromStore(store) { + // 监听 store 变化,当 Heart 初始化完成后注入 + const self = this; + let lastState = store.getState(); + + store.subscribe(() => { + const state = store.getState(); + if (state.run_states && state.run_states.run_state === 1 && !self.injected) { + // 游戏正在运行 + self.tryInject(); + } + lastState = state; + }); + } + + /** + * 设置 JIT 加速 + */ + setupJIT(heart) { + if (this.injected) return; + + try { + const rm = heart.get_runtime_manager(); + if (!rm) { + warn('Runtime manager 未找到'); + return; + } + + this.heartRM = rm; + this.injected = true; + stats.heartFound = true; + + // 拦截 Interpreter 创建 + this.patchInterpreterFactory(rm); + + log('✅ Heart JIT 加速器注入成功!'); + log(' Heart 运行时已找到,JIT 编译器就绪'); + + // 通知用户 + this.showNotification('Heart JIT 加速器已激活 ⚡'); + + } catch (e) { + warn('注入失败:', e); + stats.fallbacks++; + } + } + + /** + * Patch Interpreter 工厂方法 + * 当新的 Interpreter 实例创建时,自动替换其 step 方法 + */ + patchInterpreterFactory(rm) { + const self = this; + const jit = this.jit; + + // 拦截 load 方法(当积木加载时触发) + const origLoad = rm.load?.bind(rm); + if (origLoad) { + rm.load = function(entities) { + const result = origLoad(entities); + // 加载后 patch 所有 Interpreter + self.patchAllInterpreters(rm); + return result; + }; + } + + // 拦截 run 方法 + const origRun = rm.run?.bind(rm); + if (origRun) { + rm.run = function() { + const result = origRun(); + self.patchAllInterpreters(rm); + return result; + }; + } + + // 立即 patch 已有的 interpreter + this.patchAllInterpreters(rm); + } + + /** + * Patch 所有活跃的 Interpreter 实例 + */ + patchAllInterpreters(rm) { + // Heart 的 interpreter 通常存储在内部数组中 + // 通过遍历 rm 的属性查找 + try { + for (const key of Object.keys(rm)) { + const val = rm[key]; + if (Array.isArray(val)) { + for (const item of val) { + if (item && typeof item.step === 'function' && !this.patchedInterpreters.has(item)) { + this.patchInterpreter(item); + } + } + } + if (val && typeof val === 'object' && !Array.isArray(val)) { + for (const subKey of Object.keys(val || {})) { + const sub = val[subKey]; + if (sub && typeof sub.step === 'function' && !this.patchedInterpreters.has(sub)) { + this.patchInterpreter(sub); + } + } + } + } + } catch (e) { + if (CONFIG.debug) warn('patch interpreters 失败:', e); + } + } + + /** + * Patch 单个 Interpreter 实例 + */ + patchInterpreter(interpreter) { + if (this.patchedInterpreters.has(interpreter)) return; + this.patchedInterpreters.add(interpreter); + + const jit = this.jit; + const origStep = interpreter.step?.bind(interpreter); + + if (!origStep) return; + + // 获取根积木 + const rootBlock = interpreter.root_block; + if (!rootBlock) return; + + // JIT 编译 + const jittedFn = jit.compile(rootBlock); + if (!jittedFn) return; + + // 替换 step 方法 + interpreter.step = function() { + stats.executions++; + try { + // 尝试 JIT 执行 + const ctx = { + runtime: this.run_mgr || this.runtime_manager || {}, + data: this.program_state || {}, + eventBus: {}, + ohno: {}, + entity_id: this.original_entities?.target_entity || '', + interpreter_id: this.original_entities?.interpreter_id || '', + }; + jittedFn(ctx); + return { done: true }; + } catch (e) { + // 降级到原始解释器 + stats.fallbacks++; + return origStep(); + } + }; + + stats.jitted++; + if (CONFIG.debug) log(`Patched interpreter: ${rootBlock.type || 'unknown'}`); + } + + /** + * 显示通知 + */ + showNotification(msg) { + try { + const div = document.createElement('div'); + div.style.cssText = ` + position:fixed;top:20px;left:50%;transform:translateX(-50%); + background:linear-gradient(135deg,#0f0,#0a0);color:#fff; + padding:12px 24px;border-radius:8px;font-family:monospace;font-size:14px; + z-index:999999;box-shadow:0 4px 20px rgba(0,255,0,0.3); + animation:jit-fade 3s forwards; + `; + div.textContent = msg; + document.body.appendChild(div); + + // 添加动画 + const style = document.createElement('style'); + style.textContent = ` + @keyframes jit-fade { + 0% { opacity: 0; transform: translateX(-50%) translateY(-20px); } + 20% { opacity: 1; transform: translateX(-50%) translateY(0); } + 80% { opacity: 1; } + 100% { opacity: 0; transform: translateX(-50%) translateY(-10px); } + } + `; + document.head.appendChild(style); + + setTimeout(() => div.remove(), 3000); + } catch (e) {} + } + + /** + * 性能监控 + */ + startProfiling() { + setInterval(() => { + if (this.injected && CONFIG.debug) { + log(`Stats: jitted=${stats.jitted}, exec=${stats.executions}, fallback=${stats.fallbacks}, compile=${stats.compileTime.toFixed(1)}ms`); + } + }, CONFIG.profileInterval); } } // ================================================================ - // 性能监控面板 + // 性能面板 // ================================================================ - class PerformancePanel { + class PerfPanel { constructor() { - this.element = null; - this.updateInterval = null; + this.el = null; + this.interval = null; } - create() { - if (this.element) return; - + show() { + if (this.el) return; const panel = document.createElement('div'); panel.id = 'heart-jit-panel'; panel.style.cssText = ` - position: fixed; - top: 10px; - right: 10px; - z-index: 999999; - background: rgba(0, 0, 0, 0.85); - color: #0f0; - font-family: 'Courier New', monospace; - font-size: 12px; - padding: 12px 16px; - border-radius: 8px; - border: 1px solid #0f0; - min-width: 220px; - pointer-events: auto; - cursor: move; - user-select: none; - box-shadow: 0 0 20px rgba(0, 255, 0, 0.2); + position:fixed;top:10px;right:10px;z-index:999999; + background:rgba(0,0,0,0.9);color:#0f0;font-family:'Courier New',monospace; + font-size:11px;padding:10px 14px;border-radius:6px;border:1px solid #0f0; + min-width:200px;box-shadow:0 0 15px rgba(0,255,0,0.2); + cursor:move;user-select:none; `; - // 标题 const title = document.createElement('div'); - title.style.cssText = 'font-size: 14px; font-weight: bold; margin-bottom: 8px; color: #0f0;'; - title.textContent = '🐱 Heart JIT'; + title.style.cssText = 'font-size:13px;font-weight:bold;margin-bottom:6px;color:#0f0;border-bottom:1px solid #030;padding-bottom:4px;'; + title.textContent = '🐱 Heart JIT v3.1'; panel.appendChild(title); - // 内容 const content = document.createElement('div'); - content.id = 'heart-jit-stats'; + content.id = 'jit-stats'; panel.appendChild(content); // 拖拽 - let isDragging = false; - let offsetX, offsetY; - panel.addEventListener('mousedown', (e) => { - isDragging = true; - offsetX = e.clientX - panel.offsetLeft; - offsetY = e.clientY - panel.offsetTop; - }); - document.addEventListener('mousemove', (e) => { - if (isDragging) { - panel.style.left = (e.clientX - offsetX) + 'px'; - panel.style.top = (e.clientY - offsetY) + 'px'; - panel.style.right = 'auto'; - } - }); - document.addEventListener('mouseup', () => isDragging = false); + let dragging = false, ox, oy; + panel.addEventListener('mousedown', e => { dragging=true; ox=e.clientX-panel.offsetLeft; oy=e.clientY-panel.offsetTop; }); + document.addEventListener('mousemove', e => { if(dragging){panel.style.left=(e.clientX-ox)+'px';panel.style.top=(e.clientY-oy)+'px';panel.style.right='auto';} }); + document.addEventListener('mouseup', () => dragging=false); - // 右键菜单 - panel.addEventListener('contextmenu', (e) => { - e.preventDefault(); - CONFIG.debug = !CONFIG.debug; - log(`Debug mode: ${CONFIG.debug ? 'ON' : 'OFF'}`); - }); + // 右键切换 debug + panel.addEventListener('contextmenu', e => { e.preventDefault(); CONFIG.debug=!CONFIG.debug; }); document.body.appendChild(panel); - this.element = content; + this.el = content; - // 定期更新 - this.updateInterval = setInterval(() => this.update(), 500); + this.interval = setInterval(() => this.update(), 500); } update() { - if (!this.element) return; - this.element.innerHTML = ` -
JIT 函数: ${stats.jitted}
-
解释执行: ${stats.interpreted}
-
JIT 调用: ${stats.executions}
-
降级次数: ${stats.fallbacks}
-
编译耗时: ${stats.compileTime.toFixed(1)}ms
+ if (!this.el) return; + const s = stats; + const status = s.heartFound ? '✅ 已注入' : '⏳ 等待...'; + this.el.innerHTML = ` +
状态: ${status}
+
JIT 函数: ${s.jitted}
+
执行次数: ${s.executions}
+
降级: ${s.fallbacks}
+
编译耗时: ${s.compileTime.toFixed(1)}ms
+
右键切换debug
`; } destroy() { - if (this.updateInterval) clearInterval(this.updateInterval); - if (this.element?.parentElement) { - this.element.parentElement.remove(); - } + if (this.interval) clearInterval(this.interval); + if (this.el?.parentElement) this.el.parentElement.remove(); } } @@ -1023,35 +774,26 @@ // 主入口 // ================================================================ function main() { - log('🚀 Heart JIT 加速器 v3.0 启动'); - log(' 渲染层已经是 PIXI.js (WebGL),无需替换'); - log(' 瓶颈在 Heart 解释器 → JIT 编译为原生 JS'); + log('🚀 Heart JIT 加速器 v3.1 启动'); + log(' 渲染层: PIXI.js (WebGL) — 无需替换'); + log(' 目标: Heart 解释器 → JIT 编译为原生 JS'); - // 注入 JIT 编译器 const injector = new HeartJITInjector(); - injector.startWatching(); + injector.install(); - // 性能面板 (在播放器页面显示) - if (location.href.includes('player.codemao.cn') || - location.href.includes('ide.codemao.cn')) { - const panel = new PerformancePanel(); - // 等待 DOM 就绪 + // 性能面板 + if (location.href.includes('player.codemao.cn') || location.href.includes('ide.codemao.cn')) { + const panel = new PerfPanel(); if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', () => panel.create()); + document.addEventListener('DOMContentLoaded', () => setTimeout(() => panel.show(), 500)); } else { - setTimeout(() => panel.create(), 1000); + setTimeout(() => panel.show(), 500); } } - // 暴露 API 供调试 - window.__heartJIT = { - injector, - stats, - CONFIG, - version: '3.0.0' - }; - - log('✅ 加速器就绪,等待作品加载...'); + // 暴露 API + window.__heartJIT = { injector, stats, CONFIG, version: '3.1.0' }; + log('✅ 就绪,等待作品加载...'); } // 启动