Files
codemao-webgl-engine/userscript/codemao-heart-jit.user.js
T
openclaw cd50c395b1 v3.1: 实现 Heart 运行时注入机制
注入策略:
- 拦截 requestAnimationFrame 检测游戏循环
- 拦截 PIXI.Application 构造函数获取 pixi app
- 通过 React fiber 树遍历查找 Heart 实例
- 从 Redux store 监听作品加载状态
- Patch Interpreter.step() 为 JIT 编译版本

支持的积木:变量、逻辑、数学、文本、控制流、
过程调用、广播、时间、列表、通用 domain function
2026-05-02 22:15:28 +08:00

807 lines
27 KiB
JavaScript

// ==UserScript==
// @name 编程猫 Heart JIT 加速器
// @namespace https://gitee.com/shuiyu1123/codemao-webgl-engine
// @version 3.1.0
// @description 绕过 Heart 运行时解释器,将积木 AST 直接编译为原生 JS 执行
// @author codemao-webgl-engine
// @match https://player.codemao.cn/*
// @match https://shequ.codemao.cn/*
// @match https://kitten.codemao.cn/*
// @match https://ide.codemao.cn/*
// @run-at document-start
// @grant none
// @license MIT
// ==/UserScript==
/**
* ============================================================
* 编程猫 Heart JIT 加速器 v3.1
* ============================================================
*
* 从 player_v2.js 源码分析得到的注入点:
*
* 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 触发
*
* 注入策略:
* - 拦截 PIXI.Application 构造函数 → 拿到 pixi app 实例
* - 通过 pixi app 的 ticker 回调找到 Heart 运行时
* - 替换 Interpreter 的 step 方法为 JIT 编译版本
*
* ============================================================
*/
(function () {
'use strict';
const TAG = '🐱[Heart-JIT]';
const log = (...a) => console.log(TAG, ...a);
const warn = (...a) => console.warn(TAG, ...a);
// ================================================================
// 配置
// ================================================================
const CONFIG = {
enabled: true,
debug: false,
fallbackOnError: true,
profileInterval: 5000, // 性能采样间隔
};
// ================================================================
// 统计
// ================================================================
const stats = {
jitted: 0,
interpreted: 0,
fallbacks: 0,
executions: 0,
compileTime: 0,
avgStepTime: 0,
pixiApps: 0,
heartFound: false,
};
// ================================================================
// JIT 编译器
// ================================================================
class JITCompiler {
constructor() {
this.cache = new Map();
}
/**
* 将一个积木过程编译为原生 JS 生成器函数
* 使用生成器是为了支持 Heart 的 yield 协程机制
*/
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.emitChain(blockChain, 0);
// 生成器函数,支持 yield 协程
const fn = new Function('ctx', `
"use strict";
const { runtime, data, eventBus, ohno, entity_id, interpreter_id } = ctx;
let yield_count = 0;
let _break_flag = false;
${body}
`);
stats.compileTime += performance.now() - t0;
stats.jitted++;
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:', e);
return null;
}
}
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);
}
return code;
}
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);})()`;
}
// === 逻辑 ===
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';
// === 数学 ===
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))`;
// === 文本 ===
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 || '');
// === 控制流 ===
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}}`;
}
if (children.length > conds.length) {
code += ` else {${this.emitChain(children[children.length-1], depth+1)}}`;
}
return code;
}
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);
}
}
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);}})()`;
}
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);
}
}
// ================================================================
// 注入器 — 通过拦截 PIXI 和 RAF 找到 Heart 运行时
// ================================================================
class HeartJITInjector {
constructor() {
this.jit = new JITCompiler();
this.heartRM = null; // runtime_manager
this.injected = false;
this.patchedInterpreters = new WeakSet();
this.origRAF = window.requestAnimationFrame;
this.frameCount = 0;
}
/**
* 安装拦截器
*/
install() {
this.interceptRAF();
this.interceptPIXI();
this.startProfiling();
log('拦截器已安装,等待作品加载...');
}
/**
* 拦截 requestAnimationFrame 以检测游戏循环
*/
interceptRAF() {
const self = this;
const orig = this.origRAF;
window.requestAnimationFrame = function(cb) {
return orig.call(window, function(time) {
self.frameCount++;
// 每 120 帧尝试一次注入
if (self.frameCount % 120 === 0 && !self.injected) {
self.tryInject();
}
cb(time);
});
};
log('RAF 拦截已安装');
}
/**
* 拦截 PIXI.Application 构造函数
*/
interceptPIXI() {
const self = this;
// 等 PIXI 加载
const check = setInterval(() => {
if (window.PIXI && window.PIXI.Application) {
clearInterval(check);
self.hookPIXIApplication();
}
}, 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 app 尝试注入
*/
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);
}
return null;
}
/**
* 主注入尝试
*/
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 PerfPanel {
constructor() {
this.el = null;
this.interval = null;
}
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.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: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 = 'jit-stats';
panel.appendChild(content);
// 拖拽
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);
// 右键切换 debug
panel.addEventListener('contextmenu', e => { e.preventDefault(); CONFIG.debug=!CONFIG.debug; });
document.body.appendChild(panel);
this.el = content;
this.interval = setInterval(() => this.update(), 500);
}
update() {
if (!this.el) return;
const s = stats;
const status = s.heartFound ? '✅ 已注入' : '⏳ 等待...';
this.el.innerHTML = `
<div>状态: <span style="color:${s.heartFound?'#0f0':'#ff0'}">${status}</span></div>
<div>JIT 函数: <span style="color:#0ff">${s.jitted}</span></div>
<div>执行次数: <span style="color:#0f0">${s.executions}</span></div>
<div>降级: <span style="color:#f00">${s.fallbacks}</span></div>
<div>编译耗时: <span style="color:#888">${s.compileTime.toFixed(1)}ms</span></div>
<div style="margin-top:4px;font-size:9px;color:#555">右键切换debug</div>
`;
}
destroy() {
if (this.interval) clearInterval(this.interval);
if (this.el?.parentElement) this.el.parentElement.remove();
}
}
// ================================================================
// 主入口
// ================================================================
function main() {
log('🚀 Heart JIT 加速器 v3.1 启动');
log(' 渲染层: PIXI.js (WebGL) — 无需替换');
log(' 目标: Heart 解释器 → JIT 编译为原生 JS');
const injector = new HeartJITInjector();
injector.install();
// 性能面板
if (location.href.includes('player.codemao.cn') || location.href.includes('ide.codemao.cn')) {
const panel = new PerfPanel();
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => setTimeout(() => panel.show(), 500));
} else {
setTimeout(() => panel.show(), 500);
}
}
// 暴露 API
window.__heartJIT = { injector, stats, CONFIG, version: '3.1.0' };
log('✅ 就绪,等待作品加载...');
}
// 启动
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', main);
} else {
main();
}
})();