注入策略: - 拦截 requestAnimationFrame 检测游戏循环 - 拦截 PIXI.Application 构造函数获取 pixi app - 通过 React fiber 树遍历查找 Heart 实例 - 从 Redux store 监听作品加载状态 - Patch Interpreter.step() 为 JIT 编译版本 支持的积木:变量、逻辑、数学、文本、控制流、 过程调用、广播、时间、列表、通用 domain function
807 lines
27 KiB
JavaScript
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();
|
|
}
|
|
|
|
})();
|